Getting started

A small .NET library to make your use of MVC4 a bit more…

  • Pleasant
  • Safe —less error-prone
  • Efficient —less testing, faster refactoring
  • Succinct —less code

It allows you to define the ‘routes’ in your MVC applications in a type-safe manner. This allows the compiler to catch errors, and it allows IntelliSense to offer code completion and to prompt you with the types of URL parameters.

See the instructions for installing it and setting up a project.

The sections below show you how to use it, in comparison to the old (built-in MVC) way of doing things. This only changes the routing in your project, and links and forms in your views. Your Controllers and Actions can remain completely unchanged.

Note! The latest version, 2.2.0, fixes an issue that a couple of people reported, when running on .NET 4.5. If you’re getting a ‘Cannot find method name in expression’, update to the latest version using NuGet, and try again!

Defining a route

A simple URL route

For a simple page, the old way:

routes.MapRoute(
    name: "aboutpage",
    url: "about",
    defaults: new { controller = "HomeController", action = "About" });

The new way: first declare your route as a static variable in your SiteUrls class:

public static UrlPattern
    About = Path("about");

Then map it to a controller action in your Register method:

routes.ForController<HomeController>()
    .MapRoute(About.Get(), c => c.About);

Not only is it slightly less code, but the compiler will also check for you that you have a controller called HomeController, which has a (zero-parameter) action method called About. If you’ve misspelled it, the project will fail to compile.

public class HomeController : Controller 
{
    public ActionResult About()
    {
        return View(); // …or something.
    }
}

The Get(…) specifies that the route only matches an HTML GET request (as opposed to a POST request). You’ll need a controller with an action method as shown above.

With parameters

Here’s a route for an action which has a single URL parameter which is a constrained string. The old way would look something like this:

routes.MapRoute(
    name: "monstersincategory",
    url: "monsters/category/{category}",
    defaults: new { controller = "MonstersController", action = "ListInCategory"},
    constraints: new { category = @"[-_0-9a-zA-Z]+" });

The new way: define your route in your SiteUrls class:

public static readonly UrlPattern<string>
    MonstersInCategory = Path("monsters/category/{0}", Slug);

Then map it to a controller action in your SiteUrls.Register method:

routes.ForController<MonstersController>()
    .MapRoute(MonstersInCategory.Get(), c => c.ListInCategory);

Not only is there less code, but the compiler also checks that the ListInCategory action method has a single string parameter. If it doesn’t, the project will not compile. (Your action method would have the signature ActionResult ListIncategory(string categoryName).)

Slug is one of the predefined string types, which can include numbers, letters, hyphens and underscores. Other string types are PathComponent (which is anything allowed between the slashes in a URL—letters, numbers, and any of the characters in ‘~+-_.,’). AnyString specifies, well, any string value.

Different types of parameters

It’s quite common to want to include integer ID fields in your routes. For example:

public static readonly UrlPattern<int>
    DeleteMonster = Path("monsters/{0}/delete", Int);

This specifies a route where the middle section of the URL must be an integer. Map it to a controller action like this:

routes.ForController<MonstersController>()
    .MapRoute(DeleteMonster.Post(), c => c.Delete);

In this case, the Delete action must take a single int parameter.

A ‘POST’ containing form data

A ‘save’ post action will usually specify the id of the thing to save, and a form full of fields. Let’s imagine you have a Model class called Monster. Then define your route like this:

public static readonly RequestPattern<UrlPattern<int>, Monster>
    SaveMonster = Path("monsters/{0}", Int).Post(Body<Monster>)

Note that we defined it not just as a UrlPattern<int>, but as a RequestPattern<UrlPattern<int>, Monster>. That is: we’ve baked in not just the route, but also how it is accessed (via a POST request with a Monster payload).

You can map it to a SaveMonster(int id, Monster form) Action like this:

routes.ForController<MonstersController>()
    .MapRoute(SaveMonster, c => c.SaveMonster);

Mapping multiple routes to actions

Typically a controller will have several actions, and you would map all of them at once:

routes.ForController<MonstersController>()
    .MapRoute(MonsterList.Get(), c => c.List)
    .MapRoute(MonstersInCategory.Get(), c => c.ListCategory)
    .MapRoute(MonsterDetail.Get(), c => c.ShowNewOrExisting)
    .MapRoute(SaveMonster, c => c.SaveMonster);

Creating a link in your view file

To create a link to one of our ‘category’ pages, the old way would be this:

@Html.RouteLink("The 'mythical' category!", "monstersincategory", new { category = "mythical" })

(You need to remember to get the route name and link text the right way around, otherwise the view will fail at runtime.)

The new way:

@Html.Link("The 'mythical' category!", SiteUrls.MonstersInCategory["mythical"])

It’s shorter and there’s less chance of mixing up the route and the link text (because Visual Studio would spot it and show a squiggly red underline). Note that the parameter values are passed to the route in square brackets.

Creating a link with a particular CSS class, or other HTML attributes

As with the built-in MVC link-making things, you can specify the CSS ‘class’ attribute, or other HTML attributes in a link:

@Html.Link("The 'mythical' category!", SiteUrls.ListInCategory["mythical"], new { @class = "special" })

Generating HTML forms in your view

The old way:

@using(Html.BeginRouteForm("SaveMonster", new { id = Model.Id }, FormMethod.Post))
{
    <!-- form fields go here -->
}

The new way:

@using(Html.BeginForm(SiteUrls.SaveMonster, u => u[Model.Id]))
{
    <!-- form fields go here -->
}

Redirecting from an Action

I said that your Controllers and Actions would remain unchanged? Redirects are a little different. Old way:

return this.RedirectToAction("monstersincategory", new { category = "mythical" });

You should now write:

return this.RedirectTo(SiteUrls.MonstersInCategory["mythical"]);

Note: You need to put the this. before RedirectTo (since RedirectTo is an extension method).

File uploads

The latest version (2.2.0) now does correctly process file uploads. You just need to define a model class which contains a property of type HttpFileUploadBase, and ensure that your form specifies enctype="multipart/form-data"

Feedback?

If you have questions, feel free to post as a comment below. (I might email you back so please include your email address.)

Alternatively, ask for help on StackOverflow

If you want to contribute to the project, it’s on GitHub

7 thoughts on “Getting started

  1. I am really excited about this project! :)

    Unfortunately, I have only been able to make it work with your example project.

    When I try to add it via Nuget to my existing project, I get a runtime ArgumentException with the message “Cannot find method name in expression.” which is thrown in your GetMethodInfo() method in ControllerRouteMapper class.

    All I’m doing so far is trying to add a route for the Home Page of my project. So far, no luck.

    I’ve tried creating a fresh MVC project from scratch and adding via Nuget and get the same issue.

    Any help appreciated! :)

    –Bruce

  2. It’s really very nice project but actually I am facing the same problem,,, when I implement it, getting this error “Cannot find method name in expression.” ,
    Any Help please..Thanks in advance

    1. This should now be fixed (in version 2.2.0).

      The problem was with the way that .NET 4.5 compiles method expressions (c => c.ActionName). The new version should work with .NET 4.5, as well as with earlier versions.

  3. I don’t understand the square bracket syntax for creating links, can you explain what is happening there? Can you also provide an example with more than one route data. (Also, I think the property name for the URL in that example is wrong, shouldn’t it be MonstersInCategory?)

    Thank you!

    1. Oops, you’re absolutely right. I’ve fixed the property name.

      Square brackets are a way of ‘filling in’ in the parameters for a route, to create a URL. (It would use round brackets, but there doesn’t seem to be a way to do that in .NET/C#.) I’ll try to clarify this a little in the page.

      Will add a routing example with multiple parameters, but for now:

      static readonly UrlPattern<string, int> Test1 = Path(“some/{0}/{1}/path”, AnyString, Int);

      Creating a URL:

      @Html.Link(“Link text”, SiteUrls.Test1[“bob”, 99])

      Hope that helps!

  4. Hi Andrew,

    Thanks.

    I had never seen the [] operator implemented with > 1 arguments. But looking at the code, it seems this framework is limited to 4 URL parameters? Though it seems simple to extend it to support more than that, right?

    1. Yes, and yes. If you’ve got loads of URL parameters it’s generally better to use a query object to represent them all—but I probably should lift the 4-parameter limit at some point, perhaps to 10 or so.

Leave a Reply

Your email address will not be published. Required fields are marked *