4

so here it goes:

I have a html helper which renders ActionLinks with the optional parameters of the current url in it. this html helper also lets you add some more optional parameters as you please and merges them in 1 RouteValueDictionary.

    public static string ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller, object extraRVs, object htmlAttributes) {

        //get current optional params from current URL
        NameValueCollection c = helper.ViewContext.RequestContext.HttpContext.Request.QueryString;

        //put those in a dict
        RouteValueDictionary r = new RouteValueDictionary();
        foreach (string s in c.AllKeys) {
            r.Add(s, c[s]);
        }

        RouteValueDictionary htmlAtts = new RouteValueDictionary(htmlAttributes);

        RouteValueDictionary extra = new RouteValueDictionary(extraRVs);

        //merge them
        RouteValueDictionary m = RouteValues.MergeRouteValues(r, extra);

        return helper.ActionLink(linktext, action, controller, m, htmlAtts).ToHtmlString();
    }

this works perfect, but I now added SecurityAware Actionlinks.

so

        return helper.ActionLink(linktext, action, controller, m, htmlAtts).ToHtmlString();

becomes

        return helper.SecurityTrimmedActionLink(linktext, action, controller, m, htmlAtts);

which then calls:

   public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, object extraRVs, object htmlAttributes) {
        return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, extraRVs, htmlAttributes, false);
    }

    public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, object extraRVs, object htmlAttributes, bool showDisabled) {
        if (controller == null) {
            RouteData routeData = htmlHelper.ViewContext.RouteData;
            controller = routeData.GetRequiredString("controller");
        }
        if (IsAccessibleToUser(action, controller)) {
            return htmlHelper.ActionLink(linkText, action, controller, extraRVs, htmlAttributes).ToHtmlString();
        } else {
            return showDisabled ? String.Format("<span>{0}</span>", linkText) : "";
        }
    }

Now this does NOT work. it compiles, but my URL looks not good.

   <a count="3" keys="System.Collections.Generic.Dictionary`2+KeyCollection[System.String,System.Object]" values="System.Collections.Generic.Dictionary`2+ValueCollection[System.String,System.Object]" href="/2011-2012/Instelling?Count=3&amp;Keys=System.Collections.Generic.Dictionary%602%2BKeyCollection%5BSystem.String%2CSystem.Object%5D&amp;Values=System.Collections.Generic.Dictionary%602%2BValueCollection%5BSystem.String%2CSystem.Object%5D">Back to List</a>

as you see, what previously did work, now doesnt because it takes the RouteValueDictionarys as objects, which gives me not the result I want.

so I thought, What if i make them RouteValueDictionarys again.

       if (IsAccessibleToUser(action, controller)) {

            RouteValueDictionary parsedextraRVs = null;
            if (extraRVs != null && extraRVs.GetType().Name == "RouteValueDictionary") {
                parsedextraRVs = (RouteValueDictionary)extraRVs;
            }

            RouteValueDictionary parsedHtmlAttributes = null;
            if (htmlAttributes != null && htmlAttributes.GetType().Name == "RouteValueDictionary") {
                parsedHtmlAttributes = (RouteValueDictionary)htmlAttributes;
            }


            return htmlHelper.ActionLink(linkText, action, controller, parsedextraRVs == null ? extraRVs : parsedextraRVs, parsedHtmlAttributes == null ? htmlAttributes : parsedHtmlAttributes).ToHtmlString();
        }

but this too gives me the url i just posted above. Why did this work in my ActionLinkwParams method, but not when the ActionLinkwParams calls the SecurityTrimmedActionLink method? and how do I fix this?

Stefanvds
  • 5,868
  • 5
  • 48
  • 72

2 Answers2

7

Modify the signature of the SecurityTrimmedActionLink method to this:

public static string SecurityTrimmedActionLink(
    this HtmlHelper htmlHelper, 
    string linkText, 
    string action, 
    string controller, 
    RouteValueDictionary extraRVs, 
    RouteValueDictionary htmlAttributes
)

Notice the difference between this and this. In your case (the one that doesn't work) you are calling the second overload taking objects but in your case you are not passing anonymous objects but a RouteValueDictionary which is treated as if it was an anonymous object and its public properties (Count, Keys, Values) are serialized as attributes.

Remark: Your helper methods are not correct. They return strings. This is not how it is supposed to be. Helper methods should return MvcHtmlString.

So instead of

public static string ActionLinkwParams(...)
{
    ...
    return helper.ActionLink(linktext, action, controller, m, htmlAtts).ToHtmlString();
}

it should be:

public static MvcHtmlString ActionLinkwParams(...)
{
    ...
    return helper.ActionLink(linktext, action, controller, m, htmlAtts);
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • I cannot do that because sometimes I call that SecurityTrimmedActionLink directly and in this case I do send objects in the form of new{id=something} – Stefanvds Jan 05 '11 at 11:58
  • @Stefanvds, you will have to if you want correct attributes or you will need to make a private method and the other one public. – Darin Dimitrov Jan 05 '11 at 11:59
  • i know that this is because of the overload methods of the Actionlink, but why did this work in my first version? i cant spot any difference. i also had object's in my method.... – Stefanvds Jan 05 '11 at 12:01
  • @Stefanvd, in your first example you are calling this: http://msdn.microsoft.com/en-us/library/dd504988.aspx. Do you see that it takes RouteValueDictionary? In your second example you are calling the one with object so you need to pass an anonymous object like `new { foo = "bar" }` which should output `foo="bar"` attribute but instead you are passing a RouteValueDictionary which outputs `Keys="3" Values="..."`. – Darin Dimitrov Jan 05 '11 at 12:03
  • byt why does it not call this method http://msdn.microsoft.com/en-us/library/dd504988.aspx when i parse both 'objects' to 'routevaluedictionary's? – Stefanvds Jan 05 '11 at 12:07
  • @Stefanvds, it doesn't call it because you are passing as last argument a variable whose type is defined as `object` in your `SecurityTrimmedActionLink` method signature and this is the overload which is resolved by the compiler. Press F12 while over the method and you will see that VS will navigate to the wrong method. – Darin Dimitrov Jan 05 '11 at 12:09
  • so i need to fix it differently :) i still need to be able to call this method with anonymous types AND with routevalues. i guess i need an overload on my SecurityTrimmedActionLink – Stefanvds Jan 05 '11 at 12:15
0

So what i did in the end (thanks Darin) is made some extra overloads to make this work.

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action) {
        return SecurityTrimmedActionLink(htmlHelper, linkText, action, null, null);
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller) {
        return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, null);
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, object extraRVs) {
        return SecurityTrimmedActionLink(htmlHelper, linkText, action, null, extraRVs, null);
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, object extraRVs) {
        return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, extraRVs, null);
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, RouteValueDictionary extraRVs) {
        return SecurityTrimmedActionLink(htmlHelper, linkText, action, null, extraRVs, null);
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, RouteValueDictionary extraRVs) {
        return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, extraRVs, null);
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, object extraRVs, object htmlAttributes) {

        RouteValueDictionary rv = new RouteValueDictionary(extraRVs);
        RouteValueDictionary html = new RouteValueDictionary(htmlAttributes);

        return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, extraRVs, html);
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, RouteValueDictionary extraRVs, IDictionary<String, Object> htmlAttributes) {
        if (controller == null) {
            RouteData routeData = htmlHelper.ViewContext.RouteData;
            controller = routeData.GetRequiredString("controller");
        }
        if (IsAccessibleToUser(action, controller)) {
            return htmlHelper.ActionLink(linkText, action, controller, extraRVs, htmlAttributes);
        } else {
            return MvcHtmlString.Empty;
        }
    }
Stefanvds
  • 5,868
  • 5
  • 48
  • 72