I finally came up with the solution of using a routing constraint that can check if a parameter matches (or doesn't match) the name of a controller:
public class ControllerConstraint : IRouteConstraint
{
static List<string> ControllerNames = (from t in System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
where typeof(IController).IsAssignableFrom(t) && t.Name.EndsWith("Controller")
select t.Name.Substring(0, t.Name.Length - 10).ToLower()).ToList();
bool m_bIsController;
public ControllerConstraint(bool IsController)
{
m_bIsController = IsController;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (m_bIsController)
return ControllerNames.Contains(values[parameterName].ToString().ToLower());
else
return !ControllerNames.Contains(values[parameterName].ToString().ToLower());
}
}
Use like this:
// eg: /myusername
routes.MapRoute(
"MemberUrl",
"{member_urlid}",
new { controller = "Members", action = "View" },
new { action="View", member_urlid = new ControllerConstraint(false) }
);
// eg: /myusername/mypagename
routes.MapRoute(
"ItemUrl",
"{member_urlid}/{item_urlid}",
new { controller = "Items", action = "View" },
new { action="View", member_urlid = new ControllerConstraint(false) }
);
// Normal controller/action routes follow
The constraint new ControllerConstraint(false)
means don't match this routing rule if the parameter matches the name of a controller. Pass true to make the constraint check that the parameter does match a controller name.