Tuesday, October 15, 2013

Propagate additional URL parameter through all links generated by MVC

If you need to add some parameters to all links generated by MVC, the likely best way to do this is by adjusting ASP.NET routing. Such behavior might be interesting if you want to append some kind of a token to all links in your application. ASP.NET utilizes this technique when cookieless sessions are activated. The framework stores a session ID inside an URL and extracts it back once the server becomes a request for this URL. Another scenario where you can benefit from the parameter propagation is while making links contain a navigation history of a website visitor. The history may be further utilized by a navigation bar, which would show where the user has come from.

If you want to modify URLs before they get rendered in a view, you should create your own route class and extend the GetVirtualPath function with your customization logic.

public class TokenizedRoute : Route
{
  public TokenizedRoute(string url, IRouteHandler routeHandler) : base(url, routeHandler)
  {
  }

  public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
  {
    values.Add("token", "E0ECE7CB54EA1");

    return base.GetVirtualPath(requestContext, values);
  }
}

Next you should instruct MVC to use the created route. To this end, replace a default route inside your RegisterRoutes method with the tokenized one.

public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

  TokenizedRoute route = new TokenizedRoute("{controller}/{action}/{id}", new MvcRouteHandler());
  object defaults = new
    {
      controller = "Home",
      action = "Index",
      id = UrlParameter.Optional
    };
  route.Defaults = new RouteValueDictionary(defaults);

  routes.Add("Default", route);
}

If several routes are used in your application, just write a MapRoute extension method that creates a tokenized route and appends it to your route collection. By using this extension method, you would eliminate duplicates in the source code.

To test the previously created route, I have used a simple view with a form and an action link.

<body>
  @using (Html.BeginForm("Save", "Home"))
  {
    <div>
      @Html.LabelFor(m => m.Name)
      @Html.EditorFor(m => m.Name)
    </div>

    <button type="submit">Send</button>
  }
    
  <div>
    @Html.ActionLink("Go to About", "Index", "About")
  </div>
</body>

As you can notice, I have not passed any route values to the Html.BeginForm and Html.ActionLink helpers. However, if you would open the view in browser, you would see that each rendered URL contains a token parameter.

<form action="/Home/Save?token=E0ECE7CB54EA1" method="post">

<a href="/About?token=E0ECE7CB54EA1">Go to About</a>

The tokenized route has done a good job!

It is important to notice that such behavior is to be expected not only from Html.BeginForm and Html.ActionLink helpers, but also from other HTML helpers that utilize the ASP.NET routing mechanism. To the latter belong Html.Action, Url.Action, Url.RouteUrl, Ajax.BeginForm etc.

1 comment:

  1. Владимир, спасибо за рассказ об этом механизме!

    ReplyDelete