Channels ▼
RSS

Design

ASP.NET MVC 5.1


h3>New Attribute Routing Features

ASP.NET MVC 5 introduced a new type of routing named "attribute routing." As you might guess from its name, attribute routing enables you to use attributes to define how ASP.NET MVC matches a URI to an action instead of defining all the rules within the RouteConfig.cs file. Attribute routing can work in combination with the classic convention-based routes that have been part of ASP.NET MVC since its first release.

ASP.NET MVC 5.1 added support for constraints to attribute routing. The typical situations in which you would benefit from constraints are when you need to enable versioning or perform a header based route selection. Specifically, you can customize different aspects of attribute routes through the System.Web.Mvc.Routing.IDirectRouteFactory interface and the System.Web.Mvc.Routing.RouteFactoryAttribute class that implements this interface. You can establish constraints by extending the route prefixes through the System.Web.Mvc.Routing.IRoutePrefix interface and the System.Web.Mvc.RoutePrefixAttribute class that implements this interface. In addition, ASP.NET MVC 5.1 reports an error when it finds ambiguities in attribute routing matches and doesn't choose the first match anymore. This situation was difficult to detect and generated problems.

The ASP.NET MVC 5.1 team provides a good example of the application of constraints to attribute routing in order to enable versioning. I'll provide an example of how to use versioning.

The following code shows the VersionedRouteConstraint internal class that implements the System.Web.Routing.IRouteConstraint interface. The class determines whether the URL parameter contains a valid value for the version constraint through the implementation of the Match method. This method returns true if the URL parameter contains a valid version value; otherwise, false.

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

namespace MVC51
{
    internal class VersionedRouteConstraint : IRouteConstraint
    {
        public VersionedRouteConstraint(int version)
        {
            Version = version;
        }

        public int Version
        {
            get;
            private set;
        }

        public bool Match(HttpContextBase httpContext, 
                          Route route, 
                          string parameterName, 
                          RouteValueDictionary values, 
                          RouteDirection routeDirection)
        {
            object obj;
            if (!values.TryGetValue("version", out obj))
            {
                // No version was specified, assume Version == 1.
                return Version == 1;
            }

            int? version = obj as int?;
            if (version == Version)
            {
                return true;
            }

            int parsedVersion;
            if (!Int32.TryParse(obj as string, out parsedVersion))
            {
                // Invalid version parameter, assume no match.
                return false;
            }

            return parsedVersion == Version;
        }
    }
}

The code within the Match method is really easy to understand. If the RouteValueDictionary values don't include a value for version, the method assumes the version is 1. If a version value was specified in the route, the method tries to parse it as an int and returns whether the parsed version value matches the version value (Version) in the route constraint.

The following lines show the code for the VersionedRouteAttribute internal class that extends the System.Web.Mvc.Routing.RouteFactoryAttribute class. The class specifies a route attribute that defines a version property and creates a route entry with a constraint that matches the version parameter from the route template. The constraint is an instance of the previously defined VersionedRouteConstraint class.

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

namespace MVC51
{
    internal class VersionedRouteAttribute : RouteFactoryAttribute
    {
        public VersionedRouteAttribute(string template, int version)
            : base(template)
        {
            Version = Math.Max(1, version);
        }

        public int Version
        {
            get;
            private set;
        }

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

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

Now, it is possible to use the VersionedRoute attribute to generate a specialized route instance for the desired version number. The following lines use this attribute to specify the details for version 5. The default action to be invoked on the IncomingCallsV5Controller is Create and the id parameter is optional.

[VersionedRoute("IncomingCalls/v{version}/{action=Create}/{id?}", 5, Name = "IncomingCallsV5")]
public class IncomingCallsV5Controller : Controller

The following request paths specify the actions that they will reach in this controller:

  • /IncomingCalls/v5 will call the default IncomingCallsV5Controller.Create() action.
  • /IncomingCalls/v5/Details/8 will call IncomingCallsV5Controller.Details(8).
  • /IncomingCalls/v5/List will call IncomingCallsV5Controller.List().

The following lines show another example of the use of the VersionedRoute attribute to generate another specialized route instance for the desired version number. The code uses the attribute to specify the details for version 6. The default action to be invoked on the IncomingCallsV6Controller is List and the id parameter is optional.

[VersionedRoute("IncomingCalls/v{version}/{action=List}/{id?}", 6, Name = "IncomingCallsV6")]
public class IncomingCallsV6Controller : Controller

The following request paths specify the actions that they will reach in this controller:

  • /IncomingCalls/v6 will call the default IncomingCallsV6Controller.List() action.
  • /IncomingCalls/v6/Details/8 will call IncomingCallsV6Controller.Details(8).
  • /IncomingCalls/v6/Create will call IncomingCallsV6Controller.Create().
  • /IncomingCalls/v6/Index will call IncomingCallsV6Controller.Index().

Conclusion

The additional customization options included in ASP.NET MVC 5.1 are especially useful when you have to support versioning in your controllers. As you can see from the example, it is really easy to control versioning by adding a few simple classes and the required attributes to your controller. Combined with support for enum in views and the ability to pass in Twitter Bootstrap styles, and ASP.NET MVC 5.1 shows itself to be handy release. ASP.NET MVC 5.1 didn't add a huge amount of enhancements, but it did provide features that were high on the list of desires from developers.


Gastón Hillar is a senior contributing editor at Dr. Dobb's.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video