MVC Route Constraint to Exclude Values
For Album Credits I wanted to allow personalized urls of the format http://albumcredits.com/yournamehere. This turned out to be quite an interesting routing exercise.
Since this is an MVC app, our standard url format is of the usual http://albumcredits.com/{controller}/{action}/{index} kind, and for some pages, I need to allow the url to simply specify the controller, defaulting the action to index – again, the usual ASP.NET MVC pattern.
I was familiar with the constraint parameter option for the AddRoute method, but had never studied it in much detail – we’d used it to limit certain indexes to be numeric, but that was all. For the root-level personalized urls we needed a more robust constraint – specifically we needed to exclude any controller from the list of valid personalized Urls.
I first spent more time than I cared to trying to come up with a regular expression pattern that would NOT match the list of controller names – it looked something like this:
^(?:(?!\b(foo|bar)\b).)*$
(thanks to Justin Poliey/stackoverflow.com) where foo and bar, etc were the controller names to NOT match.
Not until after I got that to work did I think to google “mvc custom route constraint”. Of course the MS MVC team was smarter than that – custom route constraints are really very straight forward…
For my purposes, I went with David Hayden’s approach – the code below is essentially the same as his, just with the logic reversed.
using System; using System.Linq; using System.Web; using System.Web.Routing; namespace AlbumCredits.Web { /// <summary> /// Route constraint that returns true if the parameter value is not one of the excluded values. /// </summary> /// <example>A controller constraint like /// <code>new { controller = new ExcludeValuesConstraint("foo", "bar") }</code> /// will match "blah" or "snort" but will not match "foo" or "bar". /// </example> public class ExcludeValuesConstraint : IRouteConstraint { private readonly string[] _excludeValues; /// <summary> /// Initializes a new instance of the <see cref="ExcludeValuesConstraint"/> class. /// Example: <code>new { controller = new ExcludeValuesConstraint("foo", "bar") }</code> /// will match "blah" or "snort" but will not match "foo" or "bar". /// </summary> /// <param name="excludeValues">The excluded values.</param> public ExcludeValuesConstraint(params string[] excludeValues) { _excludeValues = excludeValues; } /// <summary> /// Determines whether the URL parameter contains a valid value for this constraint. /// </summary> /// <param name="httpContext">An object that encapsulates information about the HTTP request.</param> /// <param name="route">The object that this constraint belongs to.</param> /// <param name="parameterName">The name of the parameter that is being checked.</param> /// <param name="values">An object that contains the parameters for the URL.</param> /// <param name="routeDirection">An object that indicates whether the constraint check is being performed when an incoming request is being handled or when a URL is being generated.</param> /// <returns> /// true if the URL parameter contains a valid value; otherwise, false. /// </returns> public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { return !(_excludeValues.Contains(values[parameterName].ToString(), StringComparer.InvariantCultureIgnoreCase)); } } }
I can now use this when setting up my Route Table like this:
routes.MapRoute("PersonalizedUrl", /* for urls like */ "{personalizedUrl}", /* route defaults */ new { controller = MVC.Profile.Name, action = MVC.Profile.Actions.IndexByPersonalizedUrl, personalizedUrl = string.Empty }, /* where */ new { personalizedUrl = new ExcludeValuesConstraint(ControllerNameArray) } );
Labels: asp.net, asp.net mvc, howto, mvc
2 Comments:
How were you able to populate ControllerNameArray?
By Unknown, at Wednesday, January 27, 2010 3:45:00 AM
Andres - that was a manual process. I guess I could have done something with a T4 template, or potentially the T4MVC generated code may already provide that, I don't know.
In my case I used the T4MVC provided strongly typed names to create my array like this:
return new string[] {
MVC.About.Name, MVC.Admin.Name, MVC.Album.Name, MVC.Error.Name, MVC.Help.Name, MVC.Home.Name, ...
};
By Anonymous, at Friday, January 29, 2010 11:18:00 AM
Post a Comment
<< Home