views:

36

answers:

1

I'm looking for a good solution to having a URL scheme that works for both standard ASP.NET MVC controller/action urls eg:

/Home/About --> Controller "Home", Action "About"

and vanity/slug urls eg:

/fred/post  --> Controller "Posts", Action "View", User "fred", Post "post"

Importantly, I want the outbound url generation to work so that

Html.ActionLink("View", "Posts", new { User="fred", Post="post" }, null }

gives /fred/post - not /Posts/View/fred/post

It seems, I can get it to work for either inbound or outbound routing but not both. Or I can get it sort of working but it's messy and prone to breaking. What approaches, tips and tricks are there to getting something like this working cleanly?

A: 

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.

cantabilesoftware