ASP.NET MVC basics: outgoing URLs

An explanation of the different methods for generating outgoing URLs in ASP.NET MVC.

Back to blog

This blog post is an explanation of the different ways of generating outgoing URLs in an MVC application and the  pros/cons of each approach. By outgoing URLs, I mean URLs that you include in your MVC views that point to the public URLs in your MVC app – most commonly for hrefs in <a> links and action attributes in <form> tags.

It goes without saying that you don’t want to hard wire URLs as literal strings in your client side html/script as it leads to very brittle code that breaks if your URLs change in any way.  Also, the compiler cannot detect invalid URLs at build time.

MVC provides two ways of avoiding hard coded URLs,

  1. @Url.Action (and @Html.ActionLink) to generate URLs that map to a single controller action method
  2. @Url.Route (and @Html.RouteLink) to generate URLs that map to a route

 

@Url.Action

@Url.Action generates a URL for a given controller and action method.

For example, if you have an action method for handling viewing purchase orders defined as follows,

And call,

in a view, MVC looks through the route table to identity the route that  maps to controller ‘PurchaseOrder’ and the method ‘View’ and generates the URL for the route.  The last parameter is an anonymous object containing the action method parameters.

@Url.Action decouples our view code from the actual URLs so the URLs can change without breaking the application.  For example, if you change the URL \PurchaseOrder\{poNumber}\View url  to \Orders\{poNumber}\View the application will still build and run correctly.

The main problem with @Url.Action is that it sets up a dependency between your view code and your server side controller/action code.  By default, this is a dependency that is not checked at build time so a change to the name of a controller method will build correctly and fail at runtime.  You can work around this by turning on build time view compilation (see here) and using T4MVC to generate constants for your action methods.

With T4MVC as part of your project, you would use,

If you changed the name of the controller action method from ‘View’ to ‘ViewOrder’, Visual Studio will fail to build your  application.

Even with build time view compilation switched on and T4MVC installed, you still have the problem that you’ve tied the view code to the structure of your server side controller code.  I think this is unnecessary coupling.  Why should your views have a dependency on how you choose to structure your code to handle route requests ?

@Url.Route

@Url.Route generates a URL from a route name and parameters.

@Url.Route is better than @Url.Action because it only depends on the route name and parameters.  We can change the actual URL and our controllers/actions without having to modify any view code.

If our route is declared as,

To get the URL for the route we would call

If you change your URL or controllers/actions, the application will still work correctly.

Improving @Url.Route

A problem with @Url.Route is that you have to use string literals for the route name. If the route names change, the application will break at runtime.  Also, the route names could be repeated in multiple views violating the DRY principle and making them harder to maintain.

A fix is to write strongly typed UrlHelper extension methods for each route – the extensions are wrappers around the calls to RouteUrl .  For example,

And then in our view code,

This way, the string literals for route names are not repeated, the methods are easier to call, and the view code is more understandable.

As route definitions are  registered at runtime with RouteCollection  its difficult to automate generation of the route wrappers with a T4 template.

Summary

Using @Url.Route with strongly typed UrlHelper extensions is the cleanest method for generating outgoing urls – it results in the lowest coupling between client and server and the most understandable/maintainable code. It does come at the cost of having to write a little more code when compared to @Url.Action.

If you prefer the convenience of @Url.Action, use T4MVC to specify the action and turn on build time view compilation (which you should use anyway).

As you read this far, you should follow me on twitter here.