ASP.NET MVC:在不影响性能的情况下路由自定义slug

我想在我的CMS中为页面创建自定义slug,因此用户可以创建自己的SEO-urls(如Wordpress)。

我以前通过“滥用”404路由在Ruby on Rails和PHP框架中执行此操作。 无法找到请求的控制器时调用此路由,使我能够将用户路由到我的动态页面控制器以解析slug(如果没有找到页面,我将其重定向到真正的404)。 这样,仅查询数据库以检查所请求的段塞。

但是,在MVC中,只有当路由不符合/{controller}/{action}/{id}的默认路由时,才会调用catch-all路由。

为了仍然能够解析自定义slugs我修改了RouteConfig.cs文件:

 public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); RegisterCustomRoutes(routes); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { Controller = "Pages", Action = "Index", id = UrlParameter.Optional } ); } public static void RegisterCustomRoutes(RouteCollection routes) { CMSContext db = new CMSContext(); List pages = db.Pages.ToList(); foreach (Page p in pages) { routes.MapRoute( name: p.Title, url: p.Slug, defaults: new { Controller = "Pages", Action = "Show", id = p.ID } ); } db.Dispose(); } } 

这解决了我的问题,但需要为每个请求完全查询Pages表。 因为重载的show方法( public ViewResult Show(Page p) )不起作用,我还必须第二次检索页面,因为我只能传递页面ID。

  1. 有没有更好的方法来解决我的问题?
  2. 是否可以将Page对象传递给我的Show方法而不是页面ID?

即使您的路由注册代码按原样工作,问题仍然是路由在启动时静态注册。 添加新post后会发生什么 – 您是否需要重新启动应用程序池?

您可以注册包含URL的SEO slug部分的路由,然后在查找中使用slug。

RouteConfig.cs

 routes.MapRoute( name: "SeoSlugPageLookup", url: "Page/{slug}", defaults: new { controller = "Page", action = "SlugLookup", }); 

PageController.cs

 public ActionResult SlugLookup (string slug) { // TODO: Check for null/empty slug here. int? id = GetPageId (slug); if (id != null) { return View ("Show", new { id }); } // TODO: The fallback should help the user by searching your site for the slug. throw new HttpException (404, "NotFound"); } private int? GetPageId (string slug) { int? id = GetPageIdFromCache (slug); if (id == null) { id = GetPageIdFromDatabase (slug); if (id != null) { SetPageIdInCache (slug, id); } } return id; } private int? GetPageIdFromCache (string slug) { // There are many caching techniques for example: // http://msdn.microsoft.com/en-us/library/dd287191.aspx // http://alandjackson.wordpress.com/2012/04/17/key-based-cache-in-mvc3-5/ // Depending on how advanced you want your CMS to be, // caching could be done in a service layer. return slugToPageIdCache.ContainsKey (slug) ? slugToPageIdCache [slug] : null; } private int? SetPageIdInCache (string slug, int id) { return slugToPageIdCache.GetOrAdd (slug, id); } private int? GetPageIdFromDatabase (string slug) { using (CMSContext db = new CMSContext()) { // Assumes unique slugs. Page page = db.Pages.Where (p => p.Slug == requestContext.Url).SingleOrDefault (); if (page != null) { return page.Id; } } return null; } public ActionResult Show (int id) { // Your existing implementation. } 

(仅供参考:代码未编译也未经过测试 – 目前还没有我的开发环境可用。将其视为伪代码;)

这个实现将搜索每个服务器重启的slug。 您还可以在启动时预先填充键值slug-to-id缓存,这样所有现有的页面查找都会很便宜。

我编辑了我的答案,以便为您的问题提供更完整的答案:

对问题1的回答:

注册路由在启动时初始化。 (也许当Application Pool回收时,它很有可能。)我也认为你的方法没有任何问题,因为它只发生一次。 我做同样的事情从数据库查询所有支持的语言,将它们注册为/ TwoLetterISOLanguageName(/ nl,/ en,/ de等)。

对问题2的回答:

这应该可以传递一个模型:把它放在Default路由之前!

 routes.MapRoute( name: "Contact", url: "contact/{action}", defaults: new { controller = "Contact", action = "Index", MyModel = new MyModel { Name = "hello" } }); 

ContactController:

 public ActionResult Index(MyModel mymodel) { return Content(mymodel.Name); } 

该模型:

 public class MyModel { public string Name { get; set; } }