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

Posted

技术标签:

【中文标题】ASP.NET MVC:在不影响性能的情况下路由自定义 slug【英文标题】:ASP.NET MVC: Routing custom slugs without affecting performance 【发布时间】:2012-07-14 16:52:26 【问题描述】:

我想为我的 CMS 中的页面创建自定义 slug,以便用户可以创建自己的 SEO 网址(如 Wordpress)。

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

但是,在 MVC 中,只有当路由不适合 /controller/action/id 的默认路由时,才会调用包罗万象的路由。

为了仍然能够解析自定义 slug,我修改了 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 表。因为重载的 show 方法 (public ViewResult Show(Page p)) 不起作用,我还必须再次检索页面,因为我只能传递页面 ID。

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

【问题讨论】:

不是只有在应用启动时才初始化吗?顺便说一句:db.Dispose();?编辑:对不起,我没有很好地阅读你的问题。也许您可以将页面放入全局缓存中? 感谢您指出正确的方向!该函数确实只在启动时调用。我想我把它看作是一种解释性语言(如 PHP)。考虑到这是代码仅在启动时执行,我猜对性能的影响可以忽略不计。但是,我仍然不确定这是否是可行的方法,或者这是否已经可以通过使用内置功能来实现。我还想知道是否可以传递模型而不是 ID(问题 2)。 【参考方案1】:

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

您可以注册一个包含您的 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 缓存,这样所有现有的页面查找都会很便宜。

【讨论】:

不错的解决方案,但我正试图摆脱 /Page/ 部分。如果我只是将所有请求路由到一个控制器并检查请求的名称是否已经作为控制器存在,我试图避免这种方式,因为这意味着我没有使用到控制器的内置路由。但是,如果这是实现我重写的唯一方法,MVC 是否提供了查找现有控制器的方法? @christiaanderidder:如果你最后添加路线(是的,顺序很重要),就像url: "slug",它基本上就像通常的 404 hack。 @christiaanderidder:如果你实现了一个自定义的IRouteConstraint,你可以执行slug查找,如果没有.Match(...),则只需return false。我喜欢这个testable validator implementation。 第一个解决方案是我开始使用的,但使用 url: "slug" 默认路由 /Controller/Action/id 仍会捕获所有 URL。比如/custom-slug在到达catch-all路由之前会被默认路由捕获,由于控制器不存在会被路由到默认的404页面。 我假设第二种解决方案使我能够通过将其放在我的默认路由上方来检查 slug,如果 RouteConstraint 返回 false,它将转到下一个路由(默认路由)。

以上是关于ASP.NET MVC:在不影响性能的情况下路由自定义 slug的主要内容,如果未能解决你的问题,请参考以下文章

我们可以在不使用 ASP.Net Identity 的情况下使用 MVC 5 提供的 cookie 身份验证吗?

如何在不使用 Visual Studio 的情况下使用 ASP.NET MVC 实现站点?

如何在不使用 Visual Studio 的情况下使用 ASP.NET MVC 实现站点?

如何在不刷新的情况下使用 Ajax 在 ASP.NET MVC 中保存表单数据

在不使用 LINQ 和 EF 的情况下从 ASP.NET 切换到 ASP MVC

ASP.NET CORE MVC 选择标签助手 - 如何在不使用 ModelView 的情况下设置选定值