I'm trying to build friendly URLs like /post/1/my-first-post
. I started out with building my links like this:
@Html.ActionLink(Model.BlogPost.Title, "Index", "Post", new
{
id = Model.BlogPost.Id,
title = Model.FriendlyUrl
},
new { })
which were processed by this route:
routes.MapRoute(
"Post", // Route name
"posts/{id}/{title}", // URL with parameters
new { controller = "Post", action = "Index" }, // Parameter defaults
new { id = "^[0-9]+$" }
);
When I create my view model, I would give it an IUrlNiceName
which it used to create the output of FriendlyUrl
:
public string FriendlyUrl
{
get{ return UrlNiceName.ConvertToNiceName(BlogPost.Title); }
}
Then I felt like I didn't want to manage UrlNiceName
-ing on the controller level so then I build a custom Route to do this for me:
public class NiceNameRoute : RouteBase
{
private IUrlNiceNames UrlNiceNames { get; set; }
private string UrlPrefix { get { return "post/"; } }
public NiceNameRoute(IUrlNiceNames urlNiceNames)
{
UrlNiceNames = urlNiceNames;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL = httpContext.Request.AppRelativeCurrentExecutionFilePath;
//match e.g. post/1 or post/1/my-first-post
Regex regex = new Regex(UrlPrefix + "[0-9]+($|/)");
if (regex.Match(requestedURL).Success)
{
//get the start of the post id
int prefixPos = requestedURL.IndexOf(UrlPrefix, StringComparison.OrdinalIgnoreCase);
int startIdPos = prefixPos + UrlPrefix.Length;
//get the end of the post id
int endIdPos = requestedURL.IndexOf("/", startIdPos, StringComparison.OrdinalIgnoreCase);
endIdPos = endIdPos == -1 ? requestedURL.Length : endIdPos;
//get post id
string id = requestedURL.Substring(startIdPos, endIdPos - startIdPos);
result = new RouteData(this, new MvcRouteHandler());
result.Values.Add("controller", "Post");
result.Values.Add("action", "Index");
result.Values.Add("id", id);
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext,
RouteValueDictionary values)
{
VirtualPathData result = null;
if (values.ContainsKey("title")
&& Convert.ToString(values["action"]) == "Index"
&& Convert.ToString(values["controller"]) == "Post")
{
string niceName = UrlNiceNames.ConvertToNiceName(Convert.ToString(values["title"]));
result = new VirtualPathData(this, UrlPrefix + Convert.ToString(values["id"]) + "/" + niceName);
}
return result;
}
}
which I registered like this:
routes.Add(new NiceNameRoute(new HyphenUrlNiceNames()));
and generate links like this:
@Html.ActionLink(Model.BlogPost.Title, "Index", "Post", new
{
id = Model.BlogPost.Id,
title = Model.BlogPost.Title
},
new { })
I first wanted to take a crack at this but now I want to know how industry experts do it. Am I over engineering? Is there a simpler approach that is also elegant? Are there any issues with my custom Route?