2012-07-15 79 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; } 
}