I have the same need and found a solution, although I have not thoroughly tested it. My scenario is a multi-tenant API where the routes all begin with "api/{tenant}/..." The tenants are pulled from web.config, so I have the added complexity that my custom resolver should be a singleton. The solution below is for Web API but I expect it will work with in MVC with a few namespace adjustments.
Create an implementation of IHttpRouteConstraint. This is mine:
public class TenantRouteConstraint : IHttpRouteConstraint
{
public const string TenantKey = "tenant";
private readonly ISet<string> _tenants;
public TenantRouteConstraint()
{
_tenants = new HashSet<string>();
foreach (ConnectionStringSettings connectionString in ConfigurationManager.ConnectionStrings)
{
_tenants.Add(connectionString.Name.ToLowerInvariant());
}
}
private static string GetTenant(IDictionary<string, object> values)
{
object tenant;
if (values.TryGetValue(TenantKey, out tenant))
{
return tenant.ToString().ToLowerInvariant();
}
return null;
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
var tenant = GetTenant(values);
return tenant != null && _tenants.Contains(tenant);
}
}
That part was easy, then it got tricky. The routes are typically initialized in WebApiConfig.Register, which is passed as a delegated to GlobalConfiguration.Configure in Global.asax. However, the attribute based routes are not populated in the Routes collection at the end of the Register method. To get around this, I added a RegisterRouteConstraints method to WebApiConfig that gets called after Register.
The RegisterRouteConstraints method loops through the Routes collection and adds my constraint if "{tenant}" is present in the route template. The Routes collection contains three types of routes: RouteCollectionRoute, HostedHttpRoute, and LinkGeneratioRoute. The attribute based routes are in RouteCollectionRoute but these classes are internal so I can't test for the type directly. Fortunately it implements IEnumerable<IHttpRoute>
so I check for that.
public static void RegisterRouteConstraints(HttpConfiguration config)
{
var tenantConstraint = new TenantRouteConstraint();
AddConstraint(config.Routes, "tenant", tenantConstraint);
}
private static void AddConstraint(IEnumerable<IHttpRoute> routes, string key, IHttpRouteConstraint constraint)
{
foreach (var route in routes)
{
if (route.RouteTemplate.Contains("{" + key + "}") && !route.Constraints.ContainsKey(key))
{
route.Constraints.Add(key, constraint);
}
var routeCollection = route as IEnumerable<IHttpRoute>;
if (routeCollection != null)
{
AddConstraint(routeCollection, key, constraint);
}
}
}
This is called in Global.asax Application_Start:
// ...
GlobalConfiguration.Configure(WebApiConfig.Register);
GlobalConfiguration.Configure(WebApiConfig.RegisterRouteConstraints);
// ...