[转载]MVC-路由
MVC - 路由
在WebFrom里 一个请求的URL对应了服务端磁盘上的某一文件 而在MVC里却没有这种机制 MVC中URL请求都是通过Controller里面的Action方法来处理的 为了处理MVC的URL .NET平台使用了routing system(路由机制) 路由机制不是MVC特有的 而是asp.net平台的功能 也就是说在WebFrom里面也存在路由系统 路由机制检查传入的URL并计算出该请求需要的Controller和Action
URL模式
routing system使用了一套routes(注册的路由列表)每一个路由包含一个URL模式 将请求的URL与路由列表中的路由的URL模式进行比较 如果能够匹配URL模式 routing system就会将请求的URL映射为匹配成功的路由 比如路由列表中某一个路由A它的URL模式为{controller}/{action} 那么当一个这样的URL请求:http://www.cnblogs.com/Employee/Index发送到服务端 routing system会将其与路由列表中的每个路由的URL模式进行匹配 而路由A它的URL模式为{controller}/{action}正好与请求的URL相匹配 Employee匹配了controller 而Index则匹配了action 那么routing system将会提取在URL模式里面定义的segment变量的值 segment变量就是这里的controller和action 它们对应的值分别是Employee和Index routing system不是属于MVC独有的 所以它只是将请求的URL与路由的URL模式相匹配 而URL模式并不一定是{controller}/{action}这样的定义 你可以定义自己的URL模式 routing system同样会对请求和某个路由进行匹配
打开Global.asax.cs文件 有如下定义
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
}
RouteConfig的RegisterRoutes方法用于注册路由 参数RouteTable.Routes是一个RouteCollection对象 我们右击该方法转到定义 可以看到如下定义
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
routes是一个RouteCollection对象 它提供了MapRoute方法用于将一个路由添加到路由列表中 MapRoute方法有三个参数 如下
name
表示路由的名字
url
表示路由的URL模式 我们将路由的URL模式里的参数称为segment 例子中的URL模式为url: "{controller}/{action}/{id}" 所以我们说该模式有两个segment参数
defaults
表示该路由的默认值
请求的URL与segment匹配
例子中的{controller}/{action} 有两个segment 且用{}括起来 那么我们说括起来的segment参数是segment变量参数 segment变量参数可以是任意名称的参数 我们可以为变量的segment参数指定默认值 而segment也可以是常量的 即不变的量 如果一个URL模式中存在常量的segment参数 那么请求的URL中必须也具有这个参数 否则是不能匹配成功的 而变量的segment参数不是必须的 就算不提供也可以匹配成功 但必须为其指定默认值 否则无法转交给控制器处理 如
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}" 4 );
我们假设请求的URL为http://www.cnblogs.com/ 进行路由匹配时 则可以匹配MyRoute的路由(理解这一点很重要 它是匹配成功的! ) 因为它的模式中三个segment都是变量的 即 可以是任意值 甚至无值(空值) 但是这样的请求会被处理吗 答案是否定的 因为没有名称为空的controller和名称为空的action这样的类和方法 所以这种请求是会抛出错误的 但不代表请求没匹配成功 它只是在匹配成功后 MVC无法找到具体处理该请求的控制器和action方法而已 要解决这样的问题 我们可以使用默认值 就像上面介绍的default参数 我们只需要为该路由指定一个default即可
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}", 4 defaults: new { controller ="default",action = "Index" } 5 );
下面我们来看常量segment 既然是常量的 那么它必须被匹配 看例子
1 routes.MapRoute( 2 name: "myHome", 3 url: "Files/{controller}/{action}", 4 defaults: new { controller = "default", action = "Index" } 5 );
此URL模式有三个segment 一个常量的Files的参数 和两个可变的参数 Files参数必须匹配 也就是说http://www.cnblogs.com/不会匹配该路由的URL模式 既然不匹配也就不会使用默认值了 该模式只能匹配请求的URL中包含Files参数的URL
1 http://www.cnblogs.com/Files/Image/GetImgList
controller和action是可变的 我们假设服务端没有名为Image的controller和名为GetImgList的action 那么只要URL中有Files参数 则匹配成功 在转交给具体的控制器处理时 因为查不到Image和GetImgList 所以它会使用默认值 即请求的路由会被转交给名为default的控制器下的Index方法 所有可变的segment必须使用{}括起来 而常量的参数则不能括起来 否则将作为可变的参数 如
1 routes.MapRoute( 2 name: "myHome", 3 url: "Files/X{controller}/Y{action}" 4 );
像这样的URL模式 可以匹配如下URL
1 https://files.cnblogs.com/XImage/YGetImgList
routing system在处理这样的模式时 会根据{}来确定哪个是controller哪个是Action 以便转交给对应的controller和Action来处理 即 以上URL请求匹配成功后 实际上会转交给Image的GetImgList方法来处理 那么像下面的URL模式能不能匹配http://www.cnblogs.com/Shop/OldAction呢
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "Shop/OldAction", 4 defaults: new { controller = "default", action = "Index" } 5 );
答案是肯定的 Shop和OldAction是两个常量的segment 至于controller和action在请求中的URL理并没有提供它们 那么routing system会认为它们是空值 前面说过URL模式是可以匹配为空的变量segment的 所以只要常量segment匹配上了 那么就算匹配成功 接着会查找对应的控制器和action 既然两者都为空 所以该路由会将请求转交给默认值提供的控制器和action来处理
路由排序
routing system会将请求的URL与所有注册的路由的URL模式进行匹配 匹配顺序是按路由出现的先后顺序来匹配 如:
routes.MapRoute(
name: "MyRoute",
url: "{controller}/{action}",
defaults: new { controller = "default",action="index" }
);
routes.MapRoute(
name: "MyRouteTest",
url: "{controller}/{action}",
defaults: new { controller = "default", action = "Test" }
);
例子中注册了两个路由 两个路由的URL模式是一样的 那么当请求http://www.cnblogs.com/时 routing system会将最先出现的路由拿来与请求URL相匹配 它会测试请求的URL与第一个路由的URL模式是不是匹配的 如果匹配成功 则匹配就会结束 不会再去匹配下一个路由 最后改请求会转交给default控制器下的index方法而非test方法
自定义segment变量
除了controller和action 你还可以定义自己的segment变量参数 如
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}/{name}", 4 defaults: new { controller = "default",action="index",name="sam" } 5 );
以上定义了一个名为name的segment变量参数 并指定了其默认值为sam 自定义的segment变量参数也必须要有默认值 否则就算匹配成功 但也不会转交给相应的控制器处理 另外我们还可以定义任意多个segment变量参数 即在segment变量参数前加*号 则可以匹配任意多个segment变量 加*号的参数可以不指定默认值 如
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}/{name}/{*anySegment}", 4 defaults: new { controller = "default",action="index",name="sam" } 5 );
该路由的URL模式会匹配http://www.cnblogs.com/default/index/sam/any/any/any/any……
获取segment参数值
有两种方式可以获取请求的URL中的segment参数
RouteData对象的Values是一个字典集合 存储的键就是segment参数的名字 可通过键来取出参数的值 如
1 string routeDataValue = RouteData.Values["name"].ToString();
也可以通过模型绑定器自动获得segment参数的值 如
1 public ActionResult Index(string name) 2 { 3 string routeDataValue = name; 4 return View(); 5 }
路由匹配中controller的优先级
在一个大型的项目中 控制器有很多不可能放在同一个Contrllers目录中 可能会将控制器分开放在不同项目或不同的命名空间里 我们假设有两个不同的命名空间都定义了HomeContrller 如
1 namespace Course.Controllers 2 { 3 public class HomeController : Controller 4 { 5 } 6 }
另一个命名空间下也有一个HomeContrller
1 namespace Other.Controllers 2 { 3 public class HomeController : Controller 4 { 5 } 6 }
对于下面这样的路由 当请求的URL与路由的URL模式匹配成功后 routing system是该将请求转交给Course.routing system下的HomeContrller处理呢还是转交给Other.Controllers的HomeContrller处理?毕竟routing system会查找所有的controller 而不管它们在什么命名空间里
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}", 4 defaults: new { controller = "Home",action="index"} 5 );
此时具有二义性 我们应使用MapRoute的第三个参数 namespaces来指定当匹配成功后 具有多个同名的控制器时 routing system将请求转交给哪个控制器来处理以namespaces参数指定的控制器所在的命名空间为最优先 如果该命名空间下不具有HomeContrller 那么它会在其它地方查找HomeContrller 直到找到为止 所以上面的例子可以改写成这样
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}", 4 defaults: new { controller = "Home",action="index"}, 5 namespaces: new string[] { "Course.Controllers" } 6 );
以上表示的就是 当匹配成功 Course.Controllers命名空间下的Home控制器会优先被用来处理请求 namespaces是一个字符串数组 可以有多命名空间 如
1 routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}", 4 defaults: new { controller = "Home",action="index"}, 5 namespaces: new string[] { "Course.Controllers", "Other.Controllers" } 6 );
但制定两个命名空间毫无意义 因为namespaces中的命名空间有多个时 它们的优先级别是一样的 意思就是 routing system还是无法确知到底要使用哪一个命名空间下的控制器 或者namespaces中只指定一个命名空间 那么假设该命名空间没有对应名称的控制器 那么routing system还是会去其它地方查找的 要指定只在一个命名空间中查找控制器 可以在RegisterRoutes方法中使用MapRoute方法返回的Route对象的DataTokens属性 将其设为false 如
public static void RegisterRoutes(RouteCollection routes)
{
Route routeObj=routes.MapRoute(
name: "MyRoute",
url: "{controller}/{action}",
defaults: new { controller = "default",action="index"},
namespaces: new string[] { "Course.Controllers" }
);
routeObj.DataTokens["UseNamespaceFallback"] = false;
}
例子中的路由表示为当匹配成功后 default控制器应优先在Course.Controllers命名空间中查找 并且通过DataTokens["UseNamespaceFallback"]=false表示 不再去其它地方查找该控制器
路由约束
路由约束可以检测Url请求中的segment参数值 当参数值违反约束时 浏览器会提示您要找的资源已被删除、已更名或暂时不可用 有两种方式可以添加路由约束 一是使用正则式 二是自定义一个约束类
使用正则表达式添加路由约束
使用参数constraints来指定一个正则表达式 用于检测segment参数的值
Route routeObj = routes.MapRoute(
name: "MyRoute",
url: "{controller}/{action}",
defaults: new { controller = "default", action = "index" },
constraints: new { id = @"\d+" },//约束了参数id的值必须是数字
namespaces: new string[] { "Course.Other.Controllers" }
);
1 //约束了controller参数的值必须是4个数字 同时该请求必须是GET的 2 constraints: new { controller = @"\d{4}", httpMethod = new HttpMethodConstraint("GET") },
创建自定义路由约束类
如果正则表达式不能满足你的需求 你可以自定义一个路由约束的类 我们假设该约束类名为LocalhostConstraint 它的作用由你自己实现 比如可以实现只能是本机的IP登陆本网站 否则不匹配路由 要这样做 需要实现IRouteConstraint接口 该接口只有一个Match方法 该方法返回true则表示通过检测 否则表示未通过检测 以下创建了一个名为Localhost的路由约束类 它检测请求是否来自本地计算机 如果是则返回true 如果请求来自远程则返回false
namespace Course
{
public class LocalhostConstraint : IRouteConstraint
{
public bool Match (HttpContextBase httpContext,Route route,string parameterName,RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.IsLocal;
}
}
}
接着我们需要在注册的路由上使用该约束 只需要在constraints中初始化该约束类即可

1 Route routeObj = routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}/{id}", 4 defaults: new { controller = "default", action = "index" }, 5 constraints: new { LocalhostConstraint = new LocalhostConstraint() } 6 );
1 Route routeObj = routes.MapRoute( 2 name: "MyRoute", 3 url: "{controller}/{action}/{id}", 4 defaults: new { controller = "default", action = "index" }, 5 constraints: new { LocalhostConstraint = new LocalhostConstraint() } 6 );
禁用路由匹配
在MVC中 放在Views目录的Action对应的MVC视图文件在默认情况下是不能通过浏览器直接请求的 比如index.cshtml 比如客户端请求http://www.cnblogs.com/default/index.cshtml 那么浏览器会显示资源不存在 而访问一个非MVC视图文件 如xml/html文件等 是可以直接请求 它们不会被路由机制所处理 即 它们不会进入路由的URL模式匹配 而对于像一些不在磁盘上物理存在的非视图文件 比如axd文件 路由机制该做如何处理呢 可以使用RouteCollection对象的IgnoreRoute方法 此方法可以忽略对请求的URL与给定的URL模式进行匹配后的路由映射 即 只要请求的URL和IgnoreRoute方法的参数指定的URL模式匹配 则不将其转交给控制器处理 而是直接采用传统的Http处理该请求 其实RegisterRoutes方法已经预定义了对axd文件的请求不经过路由映射 如
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute(url:"{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "MyRoute", url: "{controller}/{action}", defaults: new { controller ="default",action = "Index" } ); }
出处:http://www.cnblogs.com/52XF/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。