我想在我的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。
答案 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; }
}