Reputation: 13083
How does SO perform the URL rewrite if we only put in the question ID?
questions/{id}/{whatever}
to
questions/{id}/{question-slug}
I've been working for some time with MVC and I have it working (routes, action, everything) so that it picks up the right content based on the provided ID.
However, the part after the {id}
(the slug part) stays the same as typed in. So if someone typed in content/5/foobar
it will display the right content but will leave the foobar
in there.
In the controller (or somewhere else, please suggest where) I would need to go into the DB and pull out the right slug, put it in the route data and then perform a redirect to the same action with this correct data, I guess?
This is a try with Execute Result override. It works but does not redirect or replace/display the correct URL in browser:
protected override void Execute(System.Web.Routing.RequestContext requestContext) {
if (requestContext.RouteData.Values["id"] != null) {
string currentSlug = _repository.Find(int.Parse(requestContext.RouteData.Values["id"].ToString())).Slug;
if (requestContext.RouteData.Values["slug"] != null) {
requestContext.RouteData.Values.Remove("slug");
}
requestContext.RouteData.Values.Add("slug", currentSlug);
}
base.Execute(requestContext);
}
This is another, nicely working, version of a Display
action, so you can see what it does and get an idea what I want:
//
// GET: {culture}/directory/5/{slug}
public virtual ActionResult Display(int id, string slug)
{
var model = _repository.Find(id);
if (model != null) {
if (!model.Slug.Equals(slug, System.StringComparison.OrdinalIgnoreCase)) {
return RedirectToActionPermanent(pndng.DirectoryEntry.ActionNames.Display, pndng.DirectoryEntry.Name, new { id = model.Id, slug = model.Slug });
}
return View(model);
}
// no model found
return InvokeHttp404(HttpContext);
}
This one performs permanent redirect (it does what I want) but is it right? I guess I need a redirect to refresh the browser URL, don't I?
Upvotes: 3
Views: 563
Reputation: 7195
public ActionResult Details(int id, string slug)
{
var session = MvcApplication.CurrentRavenSession;
var blogPostRelations = session
.Query<BlogPost, BlogPosts_WithRelatedData>()
.Where(x => x.IntId == id)
.As<BlogPostRelations>()
.FirstOrDefault()
;
if (blogPostRelations == null)
return HttpNotFound();
if (blogPostRelations.BlogPost.DisplayData.Slug.Value != slug)
return RedirectToActionPermanent("Details", new { id = id, slug = blogPostRelations.BlogPost.DisplayData.Slug.Value });
return View(blogPostRelations);
}
Notice the:
if (blogPostRelations.BlogPost.DisplayData.Slug.Value != slug)
return RedirectToActionPermanent("Details", new { id = id, slug = blogPostRelations.BlogPost.DisplayData.Slug.Value });
So your #2 approach is the right one.
Upvotes: 2
Reputation: 1039110
You could write a custom route for this:
public class QuestionsRoute : Route
{
public QuestionsRoute()
: base(
"questions/{id}/{slug}",
new RouteValueDictionary(new
{
controller = "questions",
action = "index",
slug = UrlParameter.Optional
}),
new RouteValueDictionary(new
{
id = @"\d+"
}),
new MvcRouteHandler()
)
{ }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
var id = rd.GetRequiredString("id");
var slug = rd.Values["slug"] as string;
if (string.IsNullOrEmpty(slug))
{
slug = GoFetchSlugFromDb(id);
if (string.IsNullOrEmpty(slug))
{
return null;
}
httpContext.Response.RedirectToRoutePermanent(new
{
action = "index",
controller = "questions",
id = id,
slug = slug
});
return null;
}
return rd;
}
private string GoFetchSlugFromDb(string id)
{
// TODO: you know what to do here
throw new NotImplementedException();
}
}
which will be registered in Application_Start
:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("questions", new QuestionsRoute());
}
Now your QuestionsController will be pretty simple:
public class QuestionsController: Controller
{
public ActionResult Index(int id, string slug)
{
...
}
}
Upvotes: 2