6

我想为我的 CMS 中的页面创建自定义 slug,以便用户可以创建自己的 SEO-url(如 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为每个请求完全查询表。因为重载的显示方法 ( public ViewResult Show(Page p)) 不起作用,我还必须再次检索页面,因为我只能传递页面 ID。

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

1 回答 1

4

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

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

于 2012-07-15T23:01:51.547 回答