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