C# Corner

ASP.NET MVC 5.1 New Features, Part 1: Attribute Routing

Eric Vogel covers some of the new features in ASP.NET MVC 5.1. In this first of a two-part series, he takes a good, long look at some new attribute routing options.

ASP.NET MVC 5.1 includes updates to attribute based routing and the stock editor templates. In this look at some of those new features in this incremental update, I'll cover some of the new attribute routing options available in ASP.NET MVC 5.1 such as the route prefixes, factories, and versioning.

To get started load up Visual Studio 2012 or Visual Studio 2013. Make sure you have the latest Update 2 if you are using Visual Studio 2013. Next create a new .NET 4.5.1 ASP.NET Web Application. Then select MVC.

First let's take a look at the new RoutePrefix attribute that lets you append a prefix to all routes within a given controller. Open up the HomeController and add the RoutePrefix attribute to the HomeController class as follows:

  [RoutePrefix("Test")]

This will change the routing prefix for the controller from "Home/" to "Test/". Additionally you need to specify a default route, in this case index:

 
  [Route("{action=index}")]

The completed HomeController should now look like Listing 1.

Listing 1: HomeController.cs

 
  using System.Web.Mvc;
  
  namespace Mvc51Demo.Controllers
  {
  [RoutePrefix("Test")]
  [Route("{action=index}")]
  public class HomeController : Controller
  {
  public ActionResult Index()
  {
  return View();
  }
  
  public ActionResult About()
  {
  ViewBag.Message = "Your application description page.";
  
  return View();
  }
  
  public ActionResult Contact()
  {
  ViewBag.Message = "Your contact page.";
  
  return View();
  }
  }
  }

You should now be able to go to /Test and get the Home Page as seen in Figure 1.

Caption

[Click on image for larger view.] Figure 1. Route Prefix on Home Controller

Another new feature in ASP.NET MVC 5.1 is the RouteFactoryAttribute class that allows you apply one to many route constraints on an MVC controller. For example you could implement a version constraint that would allow you to have multiple versions of the same page implemented as separate controllers.

The first step is to create a custom route constraint that implements IRouteConstraint that verifies that the version value specified is correct for the route. Create a new class named RouteVersionConstraint that implements IRouteConstraint. Then add an integer typed property named Version:

 
public int Version { get; private set; }

Next in the constructor I set the version property from the given value:

 
public RouteVersionConstraint(int version)
 {
     Version = version;
 }

The last step is to implement the Match method, which verifies the parameters passed to the route, in this case a valid integer version number named "version" and matches the given version value in the route:

 
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
    RouteValueDictionary values, RouteDirection routeDirection)
{
    object value;
    if (values.TryGetValue("version", out value))
    {
        int version;
        if (int.TryParse(value.ToString(), out version))
        {
            return version == Version;
        }
    }

    return false;
}

See Listing 2 for the completed RouteVersionConstaint class.

Listing 2: RouteVersionConstaint.cs

 
using System.Web;
using System.Web.Routing;

namespace Mvc51Demo
{
    public class RouteVersionConstraint : IRouteConstraint
    {
        public int Version { get; private set; }

        public RouteVersionConstraint(int version)
        {
            Version = version;
        }

        public bool Match(HttpContextBase httpContext, Route route, string parameterName,
            RouteValueDictionary values, RouteDirection routeDirection)
        {
            object value;
            if (values.TryGetValue("version", out value))
            {
                int version;
                if (int.TryParse(value.ToString(), out version))
                {
                    return version == Version;
                }
            }

            return false;
        }
    }
}

The next step is to implement the RouteVersionAttribute, which implements RouteFactoryAttribute. Create a new class file for the RouteVersionAttribute and implement the RouteFactoryAttribute. Next add an integer type Version property:

 
public int Version { get; private set; }

Next I set the Version property from the RouteVersionAttribute constructor:

 
public RouteVersionAttribute(string template, int version)
     : base(template)
 {
     Version = version;
 }

Then I override the Constraints property to return a new version constraint with the set Version property value:

 
public override RouteValueDictionary Constraints
{
    get
    {
        var constraints = new RouteValueDictionary();
        constraints.Add("version", new RouteVersionConstraint(Version));
        return constraints;
    }
}

Lastly I override the Defaults property and set the default version value to 1:

 
public override RouteValueDictionary Defaults
{
    get
    {
        var defaults = new RouteValueDictionary();
        defaults.Add("version", 1);
        return defaults;
    }
}

See Listing 3 for the completed RouteVersionAttribute class implementation.

Listing 3: RouteVersionAttribute.cs

 
using System.Web.Mvc.Routing;
using System.Web.Routing;

namespace Mvc51Demo
{
    public class RouteVersionAttribute : RouteFactoryAttribute
    {
        public int Version { get; private set; }

        public RouteVersionAttribute(string template, int version)
            : base(template)
        {
            Version = version;
        }

        public override RouteValueDictionary Constraints
        {
            get
            {
                var constraints = new RouteValueDictionary();
                constraints.Add("version", new RouteVersionConstraint(Version));
                return constraints;
            }
        }
        public override RouteValueDictionary Defaults
        {
            get
            {
                var defaults = new RouteValueDictionary();
                defaults.Add("version", 1);
                return defaults;
            }
        }
    }
}

Now it's time to try out the RouteVersion attribute. Create a new empty MVC controller named PersonController. Next add an Index.cshtml view for the Person controller's Index action as seen in Listing 4.

Listing 4: Views/Person/Index.cshtml

 
@model dynamic
<h2>Version 1</h2>

Next add the RouteVersion attribute to the PersonController with a template of "Person/{version}/{action=Index}" and a version value of 1:

 
[RouteVersion("Person/{version}/{action=Index}", 1)]
public class PersonController : Controller

See Listing 5 for the finished PersonController class.

Listing 5: PersonController.cs

 
using System.Web.Mvc;

namespace Mvc51Demo.Controllers
{
   [RouteVersion("Person/{version}/{action=Index}", 1)]
    public class PersonController : Controller
    {
        // GET: Person
        public ActionResult Index()
        {
            return View();
        }
    }
}

Now it's time to implement version 2 of the Person Controller. Add a new controller class named PersonV2Controller. Then give the PersonV2 controller class a RouteVersion attribute just like the PersonConttroller class but with a version number of 2:

 
[RouteVersion("Person/{version}/{action=Index}", 2)]
public class PersonV2Controller : Controller

Then add an Index.cshtml view for the PersonV2Controller as seen in Listing 6.

Listing 6: Views/PersonV2/Index.cshtml

 
@model dynamic
<h2>Version 2</h2>

See Listing 7 for the completed PersonV2Controller class.

Listing 7: PersonV2Controller.cs

 
using System.Web.Mvc;

namespace Mvc51Demo.Controllers
{
    [RouteVersion("Person/{version}/{action=Index}", 2)]
    public class PersonV2Controller : Controller
    {
        // GET: PersonV2
        public ActionResult Index()
        {
            return View();
        }
    }
}

You should now be able to navigate to the default person page, which should bring you to the version 1 controller as seen in Figure 2.

Caption

[Click on image for larger view.] Figure 2. Person Controller Default Version

You should also now be able to access version 2 of the Person Controller via "/Person/2" as seen in Figure 3.

Caption

[Click on image for larger view.] Figure 3. Person Controller Version 2

As you can see the new routing attributes introduced in ASP.NET 5.1 are very useful. I recommend using them when you need to simply apply complex routing on the controller level. For simpler routes adding them via the RouteConfig class is easier.

Next time, we look at bootstrap integration.

About the Author

Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].

comments powered by Disqus

Featured

Subscribe on YouTube