2012-07-15 80 views
6

我想为我的CMS中的页面创建自定义slu so,所以用户可以创建自己的SEO-urls(如Wordpress)。ASP.NET MVC:路由自定义slu without而不影响性能

我曾经在Ruby on Rails和PHP框架中通过“滥用”404路由来做到这一点。当找不到请求的控制器时调用此路由,使我能够将用户路由到我的动态页面控制器来解析slug(如果没有找到页面,我将它们重定向到真正的404)。这样数据库只被查询来检查请求的slu。。

但是,在MVC中,只有在路线不符合/{controller}/{action}/{id}的默认路线时才会调用全路径。

为了仍然能够解析定制蛞蝓我修改了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<Page> 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表来为每个请求完全查询。由于重载显示方法(public ViewResult Show(Page p))不起作用,我还必须再次检索该页面,因为我只能传递页面ID。

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

仅在应用程序启动时才初始化它吗?只是在旁注:'db.Dispose();'?编辑:对不起,我没有很好地阅读你的问题。也许你可以把页面放到全局缓存中? – Silvermind 2012-07-15 18:10:38

+0

感谢您指出正确的方向!该功能的确只在启动时调用。我想我正在看它,就好像它是一种解释型语言(如PHP)。 考虑到这是代码只在启动时执行我估计性能影响可以忽略不计。 但是,我仍然不确定这是否应该走,或者如果这已经可以通过使用内置功能实现。我还想知道是否可以通过模型而不是ID(问题2)。 – christiaanderidder 2012-07-15 18:22:38

回答

2

即使你的路线注册码工作原理是,这个问题将是路由的静态注册在启动。添加新帖子后会发生什么 - 您是否必须重新启动应用程序池?

您可以注册包含您的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. 
} 

(FYI:代码不会被编译,也不测试 - 没有得到我的提供的开发环境,现在把它看作伪代码。)

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

+0

不错的解决方案,但我试图摆脱/ Page /部分。我是否应该将所有请求路由到一个控制器,并检查请求的名称是否已作为控制器存在,我试图避免这种方式,因为这意味着我不使用内置路由到控制器。但是,如果这是实现重写的唯一方法,那么MVC是否提供了查找现有控制器的方法? – christiaanderidder 2012-07-16 11:04:49

+0

@christiaanderidder:如果你最后添加了路由(是的,顺序很重要),作为'url:“{slug}”',它基本上就像通常的404黑客一样。 – 2012-07-16 11:46:36

+1

@christiaanderidder:如果您实现自定义['IRouteConstraint'](http://msdn.microsoft.com/en-us/library/system.web.routing.irouteconstraint.aspx),您可以执行slug查找并如果它不包含'.Match(...)',则返回false。我喜欢这个[可测试的验证器实现](http://stackoverflow.com/a/9019603/)。 – 2012-07-16 11:51:56

0

我已经编辑我的答案给出更完整的回答你的问题:

回答问题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; } 
}