在 C# MVC4 中为 SEO 添加具有可变深度的自定义路径到页面
修改 MVC 路由以允许类似 /Electronics/Software/Operating-Systems/PC/Windows-8-Installer 的路由
引言
在 MVC 中,您可以对路由进行大量选择。您还可以添加过滤器、约束和捕获所有规则。但在我最近的一个项目中,我必须嵌套资源,这需要在现有的路由系统上构建。
以前,产品的 URL 被路由到正确的控制器,使用一个简单的规则,该规则使用数字产品 ID。使用规则 /{Controller}/{id}
意味着公共 URL 最终看起来像这样:/Product/2444
。当然,当找不到产品时,会使用过滤器来提供错误 404。
但是,URL 路由器可以以一种更好的方式设计,其中产品名称和类别显示在 URL 中,例如 /Electronics/Software/Operating-Systems/PC/Windows-8-Installer
- 在此方案中,最大类别深度是无限的。人们会想到创建类似 /{CategoryPath *}/{ProductPath}
的规则,该规则默认为 Product 控制器。但是,MVC 存在一个限制,即捕获所有规则(由星号表示)必须是规则中的最后一个元素。
第 1 步:Catch-All 路由
我们需要一个 catchall 路由,它有一个用于路径的单个参数。稍后我们将看看如何根据路径查找资源 - 我们有几个选择可以使用。routes.MapRoute("catchall", "{*path}", new { }, new { path = new Navigation.DatabasePageFilter() }).RouteHandler = new Navigation.RouteHandler();我添加了两个额外的项目。DatabasePageFilter - 它扩展了 IRouteConstraint 检查页面是否存在于数据库中。而 RouteHandler 根据路由上下文返回要使用的控制器和操作。
第 2 步:过滤和查找页面。
在嵌套页面时,我们有两个选择。要么每个资源都有一个绝对路径。或者每个资源都有一个相对于其前一个项目的路径,并且层次结构保存在数据库中。由于该系统允许无限深度 - 这可能是一个问题,但对于较小的深度,这可能不是一个问题。另一个需要考虑的问题是我们想多久更改一次 URL。理想情况下,一旦页面上线,其地址就不应更改。最好确保页面的 URL 不受页面祖先的变化影响。DatabasePageFilter 本身相对简单。只需检查我们是否有有效的请求上下文,然后再检查数据库中是否存在具有给定路径的 Page。public class DatabasePageFilter : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (!values.ContainsKey("path")) return false; var path = (string)values["path"]; return Page.Exists(path); } }
通过 LINQ 的魔力,Page.Exits 也非常简单。我的数据库实例包含所有页面、它们的控制器和操作的注册表。页面模型通过 RouteHandler() 直接传递给操作 - 我们稍后会讲到这一点。
public static bool Exists(string path) { return Database.GetInstance().Pages.Any(page => page.Path == path); }
第 3 步:数据库结构
我们的设计有一个小小的缺点,我们需要另一个表来存储路径。任何可以将其 URL 映射到的页面都必须继承“Page”类型,如下所示。

Page 中的两个静态方法允许我们从应用程序中的任何地方返回 Page 实例。
public partial class Page { public static Page GetByURL(string path) { return Database.GetInstance().Pages.Where(page => page.Path == path).SingleOrDefault(); } public static bool Exists(string path) { return Database.GetInstance().Pages.Any(page => page.Path == path); } public abstract string ControllerName(); public abstract string ActionName(); }
第 4 步:为模型调用正确的控制器
现在我们已经得到了我们的模型。我们需要调用正确的控制器。这比看起来容易,这要归功于添加到 Page 类的两个抽象方法。RouteHandler 做了所有艰苦的工作。目前我们保持简单,但如果需要多个操作类型或安全性,则可以扩展它。
public class RouteHandler : IRouteHandler { public IHttpHandler GetHttpHandler(RequestContext requestContext) { string path = requestContext.RouteData.Values["path"] as string; Page page = Page.GetByURL(requestContext.RouteData.Values["path"] as string); requestContext.RouteData.Values["controller"] = page.ControllerName(); requestContext.RouteData.Values["action"] = page.AcionName(); requestContext.RouteData.Values["model"] = page; return new MvcHandler(requestContext); } }
显然,每个自定义页面类型都必须重写 ControllerName 和 ActionName 方法以返回控制器的名称(例如“ProductController”)和相关操作的名称(例如“ShowProduct”)。
添加控制器再次相对简单。我们只需在参数列表中使用单个参数“model”。 public ActionResult ShowProduct(Product model) ...
基本上就是这样。可以添加很多东西来实现可扩展性。我假设读者熟悉 C#,并且能够生成数据库并访问示例中显示的模型。您可以在我的网站 https://jthorne.co.uk/ 上找到更多信息