一种重构 ASP.NET MVC 路由的方法






4.75/5 (7投票s)
本文介绍了一种以有组织的方式组织 ASP.NET MVC 路由的方法
引言
本文提出了一种组织 ASP.NET MVC 路由并重构 MapRoute 方法的方法。
背景
路由对 MVC 至关重要。路由定义了 URL 和 ActionMethod 之间的映射——将处理该 URL 的请求。例如,以下路由映射定义了当用户访问“http://mysite/shopping/cart”时,将调用 OrderController 的 ShowCart() 方法来处理该请求。
// routes.MapRoute("cart", "shopping/cart", new { controller = "Order", action = "ShowCart" });
放置路由
放置路由映射的常见位置是 Global.asax 中的 RegisterGlobalFilters 方法中:public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//Define Routes
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Login", action = "Index", id = UrlParameter.Optional } // Parameter default
);
}
}
通常,您会使用通用的路由“Controller/Action/Parameters”(如上述代码所示),该路由将 Controller/Action/parameter 与 URL 紧密映射。但我倾向于显式定义所有路由(或至少尽可能多的路由),以将 URL 与其处理方法解耦,原因如下:
- 无论您的架构有多好,当它发展时,您都会发现需要对其进行重构。由于路由与方法紧密映射,您可能会在重构时遇到困难。(不改变 URL)
- 您可能会发现需要出于搜索引擎索引目的或业务人员要求的任何其他原因更改路由(不改变操作)。
- 在复杂场景中,您可能希望为营销团队提供更改路由的选项,并定义一种将其映射到方法的机制(不改变操作)。
- 您可能希望更改被攻击的 URL(不改变操作)。
- 或者出于任何其他原因,您希望更改 URL 或 Method 的其中一个而不更改另一个。
也就是说,“路由应该明确定义”,随着时间的推移,您的路由可能会显着增长。明智的做法是重构和组织您的路由,使其易于管理,并在某种程度上逻辑分组,以便很容易找到一个路由。
本文将介绍一种组织路由的方法。
想法是创建一个名为“Routes”的文件夹,并定义单独的 .cs 文件,这些文件可以逻辑上分离路由。例如,您可能有以下结构:
- Web 解决方案
- |_ Routes [文件夹]
- |_RoutesManager.cs (稍后将详细介绍)
- |_ LoginRoutes.cs
- |_ UserRoutes.cs
- |_ ProductRoutes.cs
- |_ SignleSignOnRoutes.cs
- |_ OrderRoutes
有道理吗?我认为这比在 RegisterRoutes 中添加所有路由要整洁得多。
实现有组织的路由
建议的解决方案包含 3 个组件:
- IRouting 接口:这将定义一个“RegisterRoutes”方法,所有路由文件都将使用该方法来注册它们的路由。
- 路由:实现 IRouting 并定义路由的各个类文件。
- RoutesManager:路由管理器将通过查找实现 IRouting 的类来自动加载所有路由,而不是显式调用每个实现 IRouting 的类的 RegisterRoutes。
IRouting 接口
public interface IRouting
{
void RegisterRoutes(RouteCollection routes);
}
IRouting 定义了 RegisterRoutes,它接受 RouteCollection 作为参数。这个 RouteCollection 将是 MVC 框架使用的全局 Routes 集合,每个实现 IRouting 的类都会将它们的路由添加到这个 RouteCollection 中。
示例实现
LoginRoutes.cs
public class LoginRoutes : IRouting
{
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("loginroute", "login", new { controller = "Login", action = "Index" });
routes.MapRoute<logincontroller>("Register" x=>x.Register());*
}
}
OrderRoutes.cs
public class OrderRoutes : IRouting
{
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("buy", "placeorder/{id}", new { controller = "Orders", action = "Buy" });
routes.MapRoute("cart", "cart", new { controller = "Orders", action = "ViewCart" });
//More intuitive and VS intellisense supportive way to define routes
routes.MapRoute<OrderController>("cart/payment",r=>r.MakePayment());
<span style="font-size: 9pt;">}
</span>}
<span style="font-size: 9pt;"> </span>
路由管理器
public class RoutesManager
{
public static void RegisterRoutes(RouteCollection routes)
{
//Find all Classes implemeting IRouting
var routings = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(x => typeof(IRouting).IsAssignableFrom(x)
&& x.IsClass).ToList();
//Call Register Routes
routings.ForEach(r=>((IRouting) Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName,r.FullName).Unwrap()).RegisterRoutes(routes));
//You can also use DI
//routings.ForEach(r => ((IRouting)MvcApplication.UnityContainer.Resolve(r)).RegisterRoutes(routes));
}
}
我们编写了一个通用方法,该方法在程序集中搜索所有实现 IRouting 的类,并递归调用每个此类实现的 RegisterRoutes()。
这种方法的优点:
- 您可以将路由放在外部库/程序集中,并修改第一行以检查该程序集。这样,您可以轻松地在其他库中添加/更新路由,替换 DLL 并只需重启应用程序池即可获得所有更新的路由
- 第二行允许您在路由类中进行依赖项注入。例如,您可能希望根据 DI 从数据库或 XML 文件读取路由,然后注册这些路由。
- 您不必担心创建路由但忘记注册的潜在错误,因为这里的路由注册是无缝的。它可以从您定义的任何地方查找路由
很棒! 我们现在有了一个非常有组织和自动化的添加路由的机制。快速提示一下,我们可以扩展 MapRoute 方法,以一种更直观、视觉支持的方式来添加路由。
扩展 Map Routes
添加路由的默认方式是
routes.MapRoute(
"A_Unique_Key",
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Login", action = "Index", id = UrlParameter.Optional }
);
当您添加大量路由时,这种方法有几点不太令人满意:
1. 您可能会错误地为 2 个路由添加相同的唯一键,以便稍后在运行时识别(这很烦人)。
2. Controller 和 Action 没有智能感知支持,可能会错误输入,如果有人更改 Controller 或 Action 名称,您可能会忽略更新路由。
快速提示一下,您可以创建一个扩展方法来简化路由映射:
public static void MapRoute<T>(this RouteCollection routes, string key="", string path = "", Expression<Func<T, ActionResult>> actionMethod = null, object defaultValues = null, object constraints = null, string[] namespaces = null) where T : IController
{
var controller = typeof(T).Name.Replace("Controller", "");
routes.MapRoute(string.IsNullOrEmpty(key)?Guid.NewGuid().ToString():key, path, new { controller = controller, action = (actionMethod == null ? "Index" : ((MethodCallExpression)actionMethod.Body).Method.Name) }, constraints, namespaces);
}
1. 如果未指定 Action 方法,此扩展方法将自动将 Action 设置为“Index”。2. 指定 Controller 为 T,并使用智能感知指定 Action。
3. 此方法自动为 key 分配一个新的 Guid。对于您不期望使用 RouteLink 的路由,您可以跳过直接设置 key,以避免任何重复的 key 问题。
用法 示例
1. 这会将 LoginController 的 Index() 方法设置为处理默认 URL(http://somesite.com/)
routes.MapRoute<LoginController>("key");
2. 这会将 LoginController 的 Register() 方法设置为 URL“http://somesite.com/register”
routes.MapRoute<LoginController>("key","Register",x=>x.Register());
这只是一个快速提示。您可以发挥创意,创建更有用的扩展。
希望您喜欢我的第一篇文章。