The way I handled this before is to have two routes registered in this order
routes.MapRoute( null, "questions/{id}/{title}", new { controller = "Questions", action = "Index" }, new { id = @"\d+", title = @"[\w\-]*" }); routes.MapRoute( null, "questions/{id}", new { controller = "Questions", action = "Index" }, new { id = @"\d+" });
now in the action of the controller,
public class QuestionsController { private readonly IQuestionRepository _questionRepo; public QuestionsController(IQuestionRepository questionRepo) { _questionRepo = questionRepo; } public ActionResult Index(int id, string title) { var question = _questionRepo.Get(id); if (string.IsNullOrWhiteSpace(title) || title != question.Title.ToSlug()) { return RedirectToAction("Index", new { id, title = question.Title.ToSlug() }).AsMovedPermanently(); } return View(question); } }
We will constantly redirect the URL containing the header of the header (lowercase header with hyphens as separators) if we only have the identifier. We also verify that the title was correct by checking it against the broken version of the title of the question, thereby creating a canonical URL for the question, which contains both the identifier and the correct pool of headers.
Used by a pair of helpers
public static class PermanentRedirectionExtensions { public static PermanentRedirectToRouteResult AsMovedPermanently (this RedirectToRouteResult redirection) { return new PermanentRedirectToRouteResult(redirection); } } public class PermanentRedirectToRouteResult : ActionResult { public RedirectToRouteResult Redirection { get; private set; } public PermanentRedirectToRouteResult(RedirectToRouteResult redirection) { this.Redirection = redirection; } public override void ExecuteResult(ControllerContext context) {
source share