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

时间:2012-07-15 17:46:11

标签: c# asp.net-mvc asp.net-mvc-routing asp.net-mvc-4

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

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

但是,在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<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。

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

2 个答案:

答案 0 :(得分: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.
}

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

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

答案 1 :(得分: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; }
}