MVC路由解析---MapRoute
文章引导
引言
前面我们讲了IgnoreRoute链接
现在我们讲讲核心的MapRoute,还是提前准备Reflection工具,若是没准备,可以看“”MVC路由深入详解1---IgnoreRoute”中的System.Web.dll源码
一.RouteCollection
我们来看看RouteCollection.MapRoute,截图如下:
相信大家看到了RouteCollectionExtensions是一个静态类,是对RouteCollection的扩展(关于扩展方法的大家可以百度,此处不做详细描述)。好家伙,我们看看这个扩展方法走向何方(这个时候就是Reflection发挥作用的时候了)。
引用“”MVC路由深入详解1---IgnoreRoute”中的内容,看看MapRoute的参数传入的是什么:
name: "Default"
url: "{controller}/{action}/{id}"
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } --->这是个匿名类型
我们看看扩展方法去了哪里:
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults) => routes.MapRoute(name, url, defaults, null);
我们接着往下走
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints) =>routes.MapRoute(name, url, defaults, constraints, null);
接着
public Route MapRoute(string name,string url,object defaults,object constraints=null,string[] param=null) { if(url==null) { throw new ArgumentNullException("url"); } Route route = new Route(url, new MvcRouteHandler()) { Defaults=CreateRouteValueDictionaryUncached(defaults), Constraints=CreateRouteValueDictionaryUncached(constraints), DataTokens=new System.Web.Routing.RouteValueDictionary() }; ConstraintValidation.Validate(route); if ((param != null) && (param.Length > 0)) { route.DataTokens["Namespaces"] = param; } Add(name, route); return route; }
上面新建了一个Route,Route就是一条具体的路由 ,new Route(url, new MvcRouteHandler())传入规则url和new MvcRouteHandler()。
二.CreateRouteValueDictionaryUncached
private static System.Web.Routing.RouteValueDictionary CreateRouteValueDictionaryUncached(object values) { IDictionary<string, object> dictionary = values as IDictionary<string, object>; if (dictionary != null) { return new System.Web.Routing.RouteValueDictionary(dictionary); } return System.Web.WebPages.TypeHelper.ObjectToDictionaryUncached(values); }
三.MvcRouteHandler
我们继续拆解MvcRouteHandler
public class MvcRouteHandler: System.Web.Routing.IRouteHandler
{
System.Web.Mvc.IControllerFactory _controllerFactory;
public MvcRouteHandler() { }
public MvcRouteHandler(System.Web.Mvc.IControllerFactory controllFactory)
{
_controllerFactory = controllFactory;
}
protected virtual IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
//SetSessionStateBehavior:在派生类重写时,设置支持HTTP请求所必须的会话状态行为的类型
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}
IHttpHandler System.Web.Routing.IRouteHandler.GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
return GetHttpHandler(requestContext);
}
protected virtual System.Web.SessionState.SessionStateBehavior GetSessionStateBehavior(System.Web.Routing.RequestContext requestContext)
{
string str = (string)requestContext.RouteData.Values["controller"];
if (string.IsNullOrEmpty(str))
{
throw new InvalidOperationException(System.Web.Mvc.Properties.MvcResources.MvcRouteHandler_RouteValuesHasNoController);
}
IControllerFactory factory = _controllerFactory ?? ControllerBuilder.Current.GetControllerFactory();
return factory.GetControllerSessionBehavior(requestContext, str);
}
}
四.RouteValueDictionary
RouteValueDictionary是对Dictionary<string,object>进行包装,下面是RouteValueDictionary拆解
public RouteValueDictionary(object values) { this._dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); this.AddValues(values); } private void AddValues(object values) { if (values != null) { foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values)) { object obj2 = descriptor.GetValue(values); this.Add(descriptor.Name, obj2); } } }
我们来看看效果
五.Add(name,route)
我们来看看这个Add方法:
public void Add(string name, RouteBase item) { if (item == null) { throw new ArgumentNullException("item"); } if (!string.IsNullOrEmpty(name) && this._namedMap.ContainsKey(name)) { object[] args = new object[] { name }; //throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, System.Web.SR.GetString("RouteCollection_DuplicateName"), args), "name"); } base.Add(item); if (!string.IsNullOrEmpty(name)) { this._namedMap[name] = item; } }
里面做了重复路由名称验证,Add方法的第二个参数是RouteBase,我们看看MapRoute方法里传入给Add方法的参数是Route。Route是继承于RouteBase,RouteBase是一个抽象类,这个类是为继承类服务的,里面定义了GetRouteData和GetVirtualPath两个抽象方法。
GetRouteData:解析请求url,提取数据,如:/home/index 得到:controller/home,action/index提取得到的数据会包装成RouteData
GetVirtualPath:生成URL
六.RouteBase Route
public abstract class RouteBase { // Methods protected RouteBase(); public abstract RouteData GetRouteData(HttpContextBase httpContext); public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); }
public class Route : RouteBase { // Fields private ParsedRoute _parsedRoute; private string _url; private const string HttpMethodParameterName = "httpMethod"; // Methods public Route(string url, IRouteHandler routeHandler); public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler); public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler); public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler); public override RouteData GetRouteData(HttpContextBase httpContext); public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection); private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection); // Properties public RouteValueDictionary Constraints { get; set; } public RouteValueDictionary DataTokens { get; set; } public RouteValueDictionary Defaults { get; set; } public IRouteHandler RouteHandler { get; set; } public string Url { get; set; } }
Route添加了几个属性
Constraints:保存约束规则,最终保存为RouteValueDictionary
DataTokens:附加参数,指定controller的空间命名也放在这里。
Defaults:保存规则的默认值
url:规则URL
我们来看看GetRouteData,我们看看RouteData routeData=RouteCollection.GetRouteData(context)。
public RouteData GetRouteData(HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException("httpContext"); } if (httpContext.Request == null) { throw new ArgumentException(RoutingResources.RouteTable_ContextMissingRequest, "httpContext"); } if (!this.RouteExistingFiles) { string appRelativeCurrentExecutionFilePath = httpContext.Request.AppRelativeCurrentExecutionFilePath; if (((appRelativeCurrentExecutionFilePath != "~/") && (this._vpp != null)) && (this._vpp.FileExists(appRelativeCurrentExecutionFilePath) || this._vpp.DirectoryExists(appRelativeCurrentExecutionFilePath))) { return null; } } using (this.GetReadLock()) { foreach (RouteBase base2 in this) { RouteData routeData = base2.GetRouteData(httpContext); if (routeData != null) { return routeData; } } } return null; }
上述代码中最后通过递归遍历自己所有的路由规则,分别调用我们所有注册在RouteTable.Routes--->RouteCollection。
我们还注意到上面有这么一段:
{
string appRelativeCurrentExecutionFilePath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (((appRelativeCurrentExecutionFilePath != "~/") && (this._vpp != null)) && (this._vpp.FileExists(appRelativeCurrentExecutionFilePath) || this._vpp.DirectoryExists(appRelativeCurrentExecutionFilePath)))
{
return null;
}
}
RouteCollection 有这么一个属性RouteExistingFiles.当为false时,就检测请求的路径地址是否己经存在文件或目录,如果存在,则直接不走路由了,直接返回null,默认就是false,我们可以实验一下。当然这里是忽略了根目录的,不然默认我们 http://www.xxx.com/ 也不能访问了。
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "MVCTest.Controllers" }
);
}
这里是默认的路由注册,按理说我们访问 home 时,会去到 home controller 的 index,但是我们在在项目里加一个 home 目录,如下图。
我们再访问:http://localhost:2144/home/ 我们发现,无法找到该资源,也就是检测到home这个目录存在时,就不走路由了。
为尊重原创,本文的编写参考了以下博文和文章:
程序园: http://www.cnblogs.com/lindaohui/archive/2012/08/31/2664047.html