5

In my application, key query string parameter can be used to grant access to certain actions/views.
Now I want all ActionLinks and Forms to automatically include this parameter if present in current query string.

What is the right way to do this?

I am asking about the right way because I saw several solutions that proposed changing the views in one way or another (alternative extension methods/helpers, manual parameter passing). This is not a solution I am looking for.

UPDATE:
Final solution (based on MikeSW's anwer): https://gist.github.com/1918893.

Andrey Shchekin
  • 21,101
  • 19
  • 94
  • 162
  • This also seems relevant: http://stackoverflow.com/questions/5060346/howto-automatically-add-a-specific-value-from-current-route-to-all-generated-lin – DenNukem Nov 14 '12 at 21:41

6 Answers6

10

You can do it with routes but you need a bit more infrastrcuture. Something like this

public class RouteWithKey:Route
{
  //stuff omitted
   public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
   {
     values.Add("key", requestContext.HttpContext.Request.QueryString["key"]);   
    var t = base.GetVirtualPath(requestContext, values);        
        return t;
    } 
}

Of course you'll need to retrieve the key from the request params and handle the case where key is the second parameter in the query, but this apporach will add automatically the key to every utel constructed via the normal as.net mvc helpers

I just happen to use a custom route for an application, for a different purpose but I;ve tested with the code above and it adds the parameter, so at least in my project seems to work properly.

MikeSW
  • 16,140
  • 3
  • 39
  • 53
  • This is a good idea that is actually relevant to the question, thanks. Unfortunately, it breaks when used in ActionLink with its own parameters. It produces url with `?key=mykey?other=myother` since `ActionLink` does not expect `?` in `VirtualPath`. – Andrey Shchekin Feb 25 '12 at 23:36
  • Hooray, this actually works! Thanks a lot. I have found even easier way though: add key/value to the `values` before calling `base.GetVirtualPath`. Can you update your answer with that (since it is good to have the best solution in accepted answer)? – Andrey Shchekin Feb 26 '12 at 20:38
2

How about adding the key to route values.

{controller}/{action}/{key}

Whenever a url contains a querystring with key=somevalue, the url will look like

Home/Index/somevalue,

Now all the action links and relative urls also will include this by default.

Manas
  • 2,534
  • 20
  • 21
  • The key must be in query string (and optional), can I do it with route values? I mean that expected URL is `Home/Index?key=somevalue`. – Andrey Shchekin Feb 25 '12 at 09:45
  • no this cannot be done by route values. But still in above mentioned approach you can define key as Optional.If you want to have it in query string then you can define an extension to ActionLink menthod, that will append querystring keyvalues(if available) while creating a link. – Manas Feb 25 '12 at 11:42
0

To do this, I wrote an extension to HtmlHelper called "ActionLinkBack". The methods compose action links back to the same controller an action and merge the existing route values with any new ones that are specified.

public static HtmlString ActionLinkBack(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, object routeValues)
{
    return ActionLinkBack(htmlHelper, linkText, new RouteValueDictionary(routeValues), new RouteValueDictionary());
}

public static HtmlString ActionLinkBack(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, object routeValues, object htmlAttributes)
{
    return ActionLinkBack(htmlHelper, linkText, new RouteValueDictionary(routeValues), new RouteValueDictionary(htmlAttributes));
}

public static HtmlString ActionLinkBack(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, RouteValueDictionary routeValues)
{
    return ActionLinkBack(htmlHelper, linkText, routeValues, new RouteValueDictionary());
}

public static HtmlString ActionLinkBack(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
{
    // Build a new dictionary of route values based on the previous set
    var newRouteValues = new RouteValueDictionary(htmlHelper.ViewContext.RouteData.Values);

    // Retain current querystring parameters
    var queryString = htmlHelper.ViewContext.HttpContext.Request.QueryString;
    if (queryString.Count > 0)
    {
        foreach (string key in queryString.Keys)
        {
            newRouteValues[key] = queryString[key];
        }
    }

    // Add and override entries from the list of new route values
    if (routeValues != null)
    {
        foreach (var routeValueItem in routeValues)
        {
            newRouteValues[routeValueItem.Key] = routeValueItem.Value;
        }
    }

    return new HtmlString(htmlHelper.ActionLink(linkText, null, newRouteValues, htmlAttributes).ToHtmlString());
}

In my reusable "page navigator" view I use the extensions to compose the previous, next, and individual page links:

@Html.ActionLinkBack("Next", new { page = (int)ViewData["Page"] + 1 }, new { @class = "navigationLink" })
ShadowChaser
  • 5,520
  • 2
  • 31
  • 33
0

Hi for the situation you describe, you can easily use cookies. Create a cookie with name key and value the same as value for your key URL parameter. Now you can give or reject access to your users using the presence or absence of valid cookie, instead of presence or absence of key parameter.

You can access the cookies using Response.Cookies

If you still want to change all the Form and ActionLink helpers, then you can create your own helpers, that internally call the MVC helpers, with the extra key parameter, and use the custom helpers everywhere. Look here for writing custom helpers

Zasz
  • 12,330
  • 9
  • 43
  • 63
  • Consider "sharing", where you want to show a certain photo gallery to certain people that are not registered on site. You can just mail them a link. You could also set a cookie when they first visit, but it makes security validation more complex (we now check either query string or cookie) and creates problems if they actually want to share some specific sub-link with somebody else. Also not everyone has enabled cookies. – Andrey Shchekin Feb 25 '12 at 08:29
  • This is not exactly my current use case, but I have reasons for the current one as well, so I would prefer to implement it exactly as asked. – Andrey Shchekin Feb 25 '12 at 08:30
  • Note that I mentioned not considering custom helpers (alternative extension methods in the question), requires changes to the view. Updated question to make it more clear. – Andrey Shchekin Feb 25 '12 at 08:31
  • Do not access `Response.Cookies` directly, implement a `ValueProvider` to do it. – Jakub Konecki Feb 25 '12 at 08:33
  • Replace your MVC helpers with custom helpers, that calls MVC helpers internally. Before calling mvc helpers, you can preserve the key param from the current request. Also think about having a simple `` field, and some javascript. In all your actions you can take it in from the posted values and keep rendering it back to the response html. – Zasz Feb 25 '12 at 08:35
  • oops, I just saw that you don't want to alter your view at all, let me get back to you! – Zasz Feb 25 '12 at 08:36
-1

Having a querystring parameter that controls authorization is a bad idea IMHO. What's stopping me from adding this parameter to my url that didn't contain?

The preferred way to implement authorization is to use roles (http://msdn.microsoft.com/en-us/library/system.web.security.roleprovider.aspx).

Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
  • 2
    I am aware of this, however not all use cases and not all application require that level of security. For example, you can give someone access to your Google Document using url with a specific key. – Andrey Shchekin Feb 25 '12 at 08:34
  • @JakubKonecki take a look at capability-based security and Waterken http://higherlogics.blogspot.com/2012/01/clavis-web-security-microframework.html – Mauricio Scheffer Feb 25 '12 at 14:51
-1

The simplest thing is to put the "key" parameter in all the Url.Action and Html.ActionLink in this way:

@Url.Action("MyAction", "MyController", new { key = Request["key"] })

if Request["key"] is empty the querystring parameter will be skipped.

PopApps
  • 11
  • 1
  • 2
  • I mentioned that this is not the answer I am looking for in this question. Having framework that specifically makes cross-cutting concerns easier in most cases (`ActionFilter` etc), I am not yet ready to give up here. – Andrey Shchekin Feb 25 '12 at 23:34