白话ASP.NET MVC之一:Url 路由
好久没有写关于ASP.NET MVC的东西了,虽然《ASP.NET MVC4框架揭秘》已经完完整整的看完一遍,但是感觉和一锅粥差不多,没什么可写的,因为我自己不理解,也就写不出来。现在开始看《ASP.NET MVC5框架揭秘》,应该说第二遍了,每个代码都调试了,也看了很多的源代码,突然有一种清新的感觉,很多东西都连起来了,原来是这样啊,不不经意发出这样的感叹。既然有了一个好的理解,就整理一下,写出来,也就算巩固学习了。
言归正传吧,很多学习Asp.Net MVC的人把整个MVC请求处理的过程人为地划分成了几个小系统,分法很多了,我比较中意划分方法的是:Url路由,Controller激活,Action执行。一个请求进来,必须要经过路由系统处理,生成必要的数据,比如:Controller的名字,Action的名字,路由系统获得了Controller的名字,才会有后面的Controller的激活系统,激活了Controller,然后执行Action,返回处理结果给客户,整个流程就结束了。但是每个部分里面又包含了很多辅助的小系统来完成相应的工作。Controller的激活和Actionde执行以后再说吧,今天我们就先来说说Url路由。
本文章里面不打算翻译一个个大家都知道的名词,比如:Controller,Action,ModelBinder,ActionInvoker等等众多类型,直接用英文单词,因为翻译成中文有时候很难表示完整的意思。
一、简介
Url路由:在ASP.NET MVC系统里,来自客户端的请求总是指向一个定义在某个Controller类型中的某个Action方法,并且目标Controller和Action的名称由请求的Url决定,既URL驱动的,所以必须采取某种机制根据请求的Url地址把目标的Controller和Action的名称解析出来,我们将这种机制就称为“路由(Routing)”。但是我们要说明的是这个路由系统是独立的,不是专属ASP.NET MVC的。独立的意思是可以在ASP.NET WEB FORMS里使用,可以在ASP.NET MVC里面使用,因为路由系统专门针对MVC的特点扩展了其原有的路由系统的实现。所以我把ASP.NET的路由系统分成两个部分,可能说法不太准确,我这样分是方便我更好的理解,大家可以自行分解,便于理解就好。
第一:ASP.NET路由系统,定义在System.Web.dll程序集中,命名空间是System.Web.Routing,这个可以认为是针对ASP.NET WEB FORMS的,路由设置里面要写映射的物理.aspx文件,具体详情可以自行研究,就不多说了。
protected void Application_Start(object sender, EventArgs e)
{
var defaults = new RouteValueDictionary{ {"name","*" }, {"id","*" } };
RouteTable.Routes.MapPageRoute("","employees/{name}/{id}","~/Default.aspx",true,defaults);
}
第二:针对ASP.NET MVC扩展出来的新的路由系统,定义在System.Web.MVC.dll程序集里面。扩展类是定义在命名空间System.Web.Mvc下的RouteCollectionExtensions类型,路由注册的时候要写Controller和Action了。
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Employees", action = "GetAllEmployees", id = UrlParameter.Optional }
);
}
我们知道ASP.NET MVC是通过扩展ASP.NET处理管道实现的,这里面有两个最重要的组件,一个是实现了IHttpModule接口的UrlRoutingModule,此组件用于截获请求,进行路由解析,并重新Remap到请求的处理程序上,这个处理程序就是第二个组件,实现了IHttpHandler的MvcHandler,此组件用于激活Controller和Action方法的执行。可以这样说,路由系统的解析操作就发生在UrlRoutingModule组件里面。我们先看看他的代码,然后我们按着请求的先后顺序一步一步的介绍所涉及到的对象。
namespace System.Web.Routing
{ public class UrlRoutingModule : IHttpModule
{
private static readonly object _contextKey = new object(); private static readonly object _requestDataKey = new object(); private RouteCollection _routeCollection; public RouteCollection RouteCollection
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
set
{
this._routeCollection = value;
}
}
protected virtual void Init(HttpApplication application)
{
if (application.Context.Items[UrlRoutingModule._contextKey] != null)
{
return;
}
application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey;
application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
} private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpContextBase context = new HttpContextWrapper(((HttpApplication)sender).Context);
this.PostResolveRequestCache(context);
} public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData == null)
{
return;
}
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[]));
}
if (routeHandler is StopRoutingHandler)
{
return;
}
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[]
{
routeHandler.GetType()
}));
}
if (!(httpHandler is UrlAuthFailureHandler))
{
context.RemapHandler(httpHandler);
return;
}
if (FormsAuthenticationModule.FormsAuthRequired)
{
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
return;
}
throw new HttpException(, SR.GetString("Assess_Denied_Description3"));
}
}
}
二、路由解析的先后顺序
路由规则注册-----》截获请求-----》路由解析并获得RouteData对象-----》根据RouteData的RouteHandler获得MvcRouteHandler对象-----》根据MvcRouteHandler获得MvcHandler对象-----》请求对象重新路由,HttpContext.Remap(MvcHandler)-----》MvcHandler接管请求处理-----》Controller激活-----》Action方法的执行-----》返回处理结果并结束
上面是路由解析的全过程,我再用白话描述一遍:要想解析路由,必须先注册路由规则的对象吧,也就是Route对象,连注册都没有还解析个什么劲啊,我们一般在Global.asax文件的Application_Start方法里面注册Route对象。注册好了路由规则,启动系统,早已注册好的UrlRoutingModule截获请求,用当前的请求的Url和路由表【RouteTable】里面存储的路由对象【Route】进行比较,其实是Url地址和路由对象【Route】的路由地址模板Url进行匹配,没有匹配就返回空值,如果多个匹配,就选择第一个匹配路由对象【Route】,根据选择的路由对象【Route】生成路由数据【RouteData】。因为路由数据【RouteData】包含RouteHandler属性,RouteHandler属性用于提供最终处理请求的HttpHandler,ASP.NET MVC中RouteHandler的属性值就是MvcRouteHandler,MvcRouteHandler实现了IRouteHandler接口,这个接口有一个方法GetHttpHandler,这个方法就提供了用于处理最终请求的HttpHandler,这个HttpHandler就是MvcHandler,好了,该获取的对象都准备好了,那就把请求交给MvcHandler吧,交接是通过HttpContext.Remap方实现的,好了,大概就是这么一个过程。
我先简要的把路由解析所涉及到的类型说一下,我们是面向对象编程的,所以很多东西已经对象化了,说的不错。哈哈老王卖瓜了:
1、Route:路由规则抽象获得RouteBase类型,此类型是抽象类,他有唯一的一个子类就是Route对象,路由规则对象肯定要有路由模板的地址Url,要有路由的默认值了,路由的约束值了等等一些东西。也可以这样理解,我们注册的每一个规则就是一个Route对象,每一个Route对象实例就是代表一种路由的规则。
2、UrlRoutingModule:我们有了Route路由规则对象,也注册好了,系统启动,我们要把请求截获,不截获请求,就没办法处理了,所以我们就是扩展了ASP.Net处理管道,实现了IHttpModule接口,定义了UrlRoutingModule类型,它用于截获请求,进行路由解析,我上面也提到过该类,并贴出了代码,下面会详细说的,非常核心的类,如果对ASP.NET处理管道不熟悉的可以去网上查找一些资料,很容易找的。
3、RouteTable:ASP.NET MVC有一个代表全局的路由表,所有注册Route对象都存在RouteTable.Routes表示的集合里面,路由解析的时候就是和RouteTable.Routes表示的路由表里面的每一个Route对象进行匹配,如果RouteTable里面的所有Route对象所代表的路由规则和当前的Url都不匹配就返回Null值,如果有多个匹配就选择第一个匹配的Route对象,并根据Route对象生成RouteData对象。
4、RouteData:当请求的Url和RouteTable路由表中表示路由规则的Route相匹配的时候,会根据匹配的Route对象生成RouteData,它里面包含了根据Url解析所得到东西,如:controller的名字,Action的名字等信息。
5、MvcRouteHandler:MvcRouteHandler是MvcHandler对象的提供对象,RouteData的RouteHandler属性的值针对MVC来说就是MvcRouteHandler,如果是ASP.NET的路由系统,那就是PageRouteHandler对象了。
6、MvcHandler:既然我们获得了MvcHandler,通过HttpContext的Remap方法重新路由,把请求交给MvcHandler来处理,后面就是Controller激活和Action方法的解析了。
好了,简单的说了一下每个对象的用途,大家也许有了一个大概的印象了吧,我们下面就来详细的说说每一个对象的实际情况。
三、路由对象的详解
我们使用的是面向对象的语言,所操作的一切都是对象,路由规则经过抽象就是RouteBase对象,有了RouteBase对象,我们才可以注册路由对象,才有针对RouteBase的路由解析。接下来就让我们开始说我们的第一个对象吧,Route路由对象,刚才不是说要说RouteBase,咱们现在又要说Route对象了,怎么变了,其实没变,两个对象是一回事。RouteBase其实是 一个抽象类,我们所指的或者所说的Route路由对象,其实都是从RouteBase对象继承下来的。
1、RouteBase和Route
我们看看RouteBase的源代码吧,不看源代码,很多东西不能搞清楚的。
public abstract class RouteBase
{
private bool _routeExistingFiles = true; public bool RouteExistingFiles
{
get
{
return this._routeExistingFiles;
}
set
{
this._routeExistingFiles = value;
}
} public abstract RouteData GetRouteData(HttpContextBase httpContext); public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
}
RouteBase有两个抽象方法,第一个是返回的是RouteData类型的GetRouteData方法,RouteData说白了就是路由数据,在白点就是根据路由规则解析获得的数据,所以方法名是GetRouteData,该方法的参数是HttpContextBase对象,这个对象表示的当前请求。或者说这个方法就是根据当前的Url请求和路由规则对象进行比较,如果匹配就根据路由规则对象Route生成对象的RouteData路由数据对象。另外一个抽象方法就是,返回类型为VirtualPathData对象的GetVirtualPath方法,此方法的作用就是根据提供的数据和注册的路由规则生成相应的虚拟路径。RouteData稍后会将,让我们看看VirtualPathData是一个什么样的东西,源代码如下:
public class VirtualPathData
{
private string _virtualPath; private RouteValueDictionary _dataTokens = new RouteValueDictionary(); public RouteValueDictionary DataTokens
{
get
{
return this._dataTokens;
}
} public RouteBase Route
{
get;
set;
} public string VirtualPath
{
get
{
return this._virtualPath ?? string.Empty;
}
set
{
this._virtualPath = value;
}
} public VirtualPathData(RouteBase route, string virtualPath)
{
this.Route = route;
this.VirtualPath = virtualPath;
}
}
返回字符串类型VirtualPath属性就是生成虚拟路径,返回类型RouteBase的Route属性表示的匹配规则那个RouteBase对象。现在我想访问真实存在的一个物理文件怎么办呢?RouteBase有一个RouteExistingFiles属性,这个属性表示是否路由物理存在的文件,默认值是True,意味着我们想访问某个物理文件在不改变设置的情况下是不行的,因为已经按着路由规则发生了路由了。
我们在来看看Route对象吧,源码如下:
public class Route : RouteBase
{
private const string HttpMethodParameterName = "httpMethod"; private string _url; private ParsedRoute _parsedRoute; public RouteValueDictionary Constraints
{
get;
set;
} public RouteValueDictionary DataTokens
{
get;
set;
} public RouteValueDictionary Defaults
{
get;
set;
} public IRouteHandler RouteHandler
{
get;
set;
} public string Url
{
get
{
return this._url ?? string.Empty;
}
set
{
this._parsedRoute = RouteParser.Parse(value);
this._url = value;
}
} public Route(string url, IRouteHandler routeHandler)
{
this.Url = url;
this.RouteHandler = routeHandler;
} public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
{
this.Url = url;
this.Defaults = defaults;
this.RouteHandler = routeHandler;
} public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
{
this.Url = url;
this.Defaults = defaults;
this.Constraints = constraints;
this.RouteHandler = routeHandler;
} public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
{
this.Url = url;
this.Defaults = defaults;
this.Constraints = constraints;
this.DataTokens = dataTokens;
this.RouteHandler = routeHandler;
} public override RouteData GetRouteData(HttpContextBase httpContext)
{
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring() + httpContext.Request.PathInfo;
RouteValueDictionary routeValueDictionary = this._parsedRoute.Match(virtualPath, this.Defaults);
if (routeValueDictionary == null)
{
return null;
}
RouteData routeData = new RouteData(this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, routeValueDictionary, RouteDirection.IncomingRequest))
{
return null;
}
foreach (KeyValuePair<string, object> current in routeValueDictionary)
{
routeData.Values.Add(current.Key, current.Value);
}
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> current2 in this.DataTokens)
{
routeData.DataTokens[current2.Key] = current2.Value;
}
}
return routeData;
} public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
BoundUrl boundUrl = this._parsedRoute.Bind(requestContext.RouteData.Values, values, this.Defaults, this.Constraints);
if (boundUrl == null)
{
return null;
}
if (!this.ProcessConstraints(requestContext.HttpContext, boundUrl.Values, RouteDirection.UrlGeneration))
{
return null;
}
VirtualPathData virtualPathData = new VirtualPathData(this, boundUrl.Url);
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> current in this.DataTokens)
{
virtualPathData.DataTokens[current.Key] = current.Value;
}
}
return virtualPathData;
} protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
IRouteConstraint routeConstraint = constraint as IRouteConstraint;
if (routeConstraint != null)
{
return routeConstraint.Match(httpContext, this, parameterName, values, routeDirection);
}
string text = constraint as string;
if (text == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[]
{
parameterName,
this.Url
}));
}
object value;
values.TryGetValue(parameterName, out value);
string arg_7C_0 = Convert.ToString(value, CultureInfo.InvariantCulture);
string pattern = "^(" + text + ")$";
return Regex.IsMatch(arg_7C_0, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
} private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection)
{
if (this.Constraints != null)
{
foreach (KeyValuePair<string, object> current in this.Constraints)
{
if (!this.ProcessConstraint(httpContext, current.Value, current.Key, values, routeDirection))
{
return false;
}
}
return true;
}
return true;
}
}
其实代码不复杂,大家也应该看的懂,Route对象直接继承RouteBase对象的,而且是唯一一个这样的对象,既然是路由规则对象,肯定包括,地址模板,默认值,约束条件和一些附加的数据,Constraints保存的就是约束条件,Defaults保存的就是默认值,Url属性就是地址模板了。他一定要实现GetRouteData方法和GetVirtualPath方法
2、RouteData
我们有了路由规则了,也就是Route对象,我们也注册了,接下来就是路由解析,就是和Route对象的的Url进行比较,如果匹配就生成了RouteData对象,也就是Route对象GetRouteData方法返回结果了。大家一定要记住,RouteData是基于Route对象生成的,我们看看源码吧:
public class RouteData
{
private IRouteHandler _routeHandler; private RouteValueDictionary _values = new RouteValueDictionary(); private RouteValueDictionary _dataTokens = new RouteValueDictionary(); public RouteValueDictionary DataTokens
{
get
{
return this._dataTokens;
}
} public RouteBase Route
{
get;
set;
} public IRouteHandler RouteHandler
{
get
{
return this._routeHandler;
}
set
{
this._routeHandler = value;
}
} public RouteValueDictionary Values
{
get
{
return this._values;
}
} public RouteData()
{
} public RouteData(RouteBase route, IRouteHandler routeHandler)
{
this.Route = route;
this.RouteHandler = routeHandler;
} public string GetRequiredString(string valueName)
{
object obj;
if (this.Values.TryGetValue(valueName, out obj))
{
string text = obj as string;
if (!string.IsNullOrEmpty(text))
{
return text;
}
}
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("RouteData_RequiredValue"), new object[]
{
valueName
}));
}
}
RouteData对象是规则匹配所要生成的东西,根据Url解析获得数据存在Values属性里面,DataTokens属性表示一些附加的数据,并且这个数据来源于Route对象的DataTokens属性,RouteData对象RouteHandler属性的值也是来源于Route对象的RouteHandler属性,这个RouteHandler在ASP.NET路由系统就是PageRouteHandler,在ASP.NET MVC中就是MvcRouteHandler,用于提供最终处理请求的HttpHandler。RouteData对象还有一个Route属性,此属性表示在路由解析的时候匹配的那个Route路由规则对象。
之所以说RouteData是基于Route对象产生了,因为RouteData对象里面的很多值来源于Routed对象,Route对象是基础。
3、RouteTable
当我们了有了路由规则Route对象的时候,这些路由对象放在什么地方呢?答案就是放在了路由表RouteTable对象中,我们先看看他的源码吧:
public class RouteTable
{
private static RouteCollection _instance = new RouteCollection(); public static RouteCollection Routes
{
get
{
return RouteTable._instance;
}
}
}
RouteTable有一个静态属性是Routes,此属性的类型是RouteCollection,字面意思Route的Collection,就是路由对象的集合,类型如下:
public class RouteCollection : Collection<RouteBase>
{
private class ReadLockDisposable : IDisposable
{
private ReaderWriterLockSlim _rwLock; public ReadLockDisposable(ReaderWriterLockSlim rwLock)
{
this._rwLock = rwLock;
} void IDisposable.Dispose()
{
this._rwLock.ExitReadLock();
}
} private class WriteLockDisposable : IDisposable
{
private ReaderWriterLockSlim _rwLock; public WriteLockDisposable(ReaderWriterLockSlim rwLock)
{
this._rwLock = rwLock;
} void IDisposable.Dispose()
{
this._rwLock.ExitWriteLock();
}
} private sealed class IgnoreRouteInternal : Route
{
public IgnoreRouteInternal(string url) : base(url, new StopRoutingHandler())
{
} public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues)
{
return null;
}
} private Dictionary<string, RouteBase> _namedMap = new Dictionary<string, RouteBase>(StringComparer.OrdinalIgnoreCase); private VirtualPathProvider _vpp; private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); public bool AppendTrailingSlash
{
get;
set;
} public bool LowercaseUrls
{
get;
set;
} public bool RouteExistingFiles
{
get;
set;
} private VirtualPathProvider VPP
{
get
{
if (this._vpp == null)
{
return HostingEnvironment.VirtualPathProvider;
}
return this._vpp;
}
set
{
this._vpp = value;
}
} public RouteBase this[string name]
{
get
{
if (string.IsNullOrEmpty(name))
{
return null;
}
RouteBase result;
if (this._namedMap.TryGetValue(name, out result))
{
return result;
}
return null;
}
} public RouteCollection()
{
} public RouteCollection(VirtualPathProvider virtualPathProvider)
{
this.VPP = virtualPathProvider;
} public void Add(string name, RouteBase item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
if (!string.IsNullOrEmpty(name) && this._namedMap.ContainsKey(name))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("RouteCollection_DuplicateName"), new object[]
{
name
}), "name");
}
base.Add(item);
if (!string.IsNullOrEmpty(name))
{
this._namedMap[name] = item;
}
} public Route MapPageRoute(string routeName, string routeUrl, string physicalFile)
{
return this.MapPageRoute(routeName, routeUrl, physicalFile, true, null, null, null);
} public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess)
{
return this.MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, null, null, null);
} public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults)
{
return this.MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, null, null);
} public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints)
{
return this.MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, constraints, null);
} public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens)
{
if (routeUrl == null)
{
throw new ArgumentNullException("routeUrl");
}
Route route = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess));
this.Add(routeName, route);
return route;
} protected override void ClearItems()
{
this._namedMap.Clear();
base.ClearItems();
} public IDisposable GetReadLock()
{
this._rwLock.EnterReadLock();
return new RouteCollection.ReadLockDisposable(this._rwLock);
} private RequestContext GetRequestContext(RequestContext requestContext)
{
if (requestContext != null)
{
return requestContext;
}
HttpContext expr_0A = HttpContext.Current;
if (expr_0A == null)
{
throw new InvalidOperationException(SR.GetString("RouteCollection_RequiresContext"));
}
return new RequestContext(new HttpContextWrapper(expr_0A), new RouteData());
} private bool IsRouteToExistingFile(HttpContextBase httpContext)
{
string appRelativeCurrentExecutionFilePath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
return appRelativeCurrentExecutionFilePath != "~/" && this.VPP != null && (this.VPP.FileExists(appRelativeCurrentExecutionFilePath) || this.VPP.DirectoryExists(appRelativeCurrentExecutionFilePath));
} public RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (httpContext.Request == null)
{
throw new ArgumentException(SR.GetString("RouteTable_ContextMissingRequest"), "httpContext");
}
if (base.Count == )
{
return null;
}
bool flag = false;
bool flag2 = false;
if (!this.RouteExistingFiles)
{
flag = this.IsRouteToExistingFile(httpContext);
flag2 = true;
if (flag)
{
return null;
}
}
using (this.GetReadLock())
{
foreach (RouteBase current in this)
{
RouteData routeData = current.GetRouteData(httpContext);
if (routeData != null)
{
RouteData result;
if (!current.RouteExistingFiles)
{
if (!flag2)
{
flag = this.IsRouteToExistingFile(httpContext);
}
if (flag)
{
result = null;
return result;
}
}
result = routeData;
return result;
}
}
}
return null;
} private string NormalizeVirtualPath(RequestContext requestContext, string virtualPath)
{
string text = Util.GetUrlWithApplicationPath(requestContext.HttpContext, virtualPath);
if (this.LowercaseUrls || this.AppendTrailingSlash)
{
int num = text.IndexOfAny(new char[]
{
'?',
'#'
});
string text2;
string str;
if (num >= )
{
text2 = text.Substring(, num);
str = text.Substring(num);
}
else
{
text2 = text;
str = "";
}
if (this.LowercaseUrls)
{
text2 = text2.ToLowerInvariant();
}
if (this.AppendTrailingSlash && !text2.EndsWith("/"))
{
text2 += "/";
}
text = text2 + str;
}
return text;
} public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
requestContext = this.GetRequestContext(requestContext);
using (this.GetReadLock())
{
using (IEnumerator<RouteBase> enumerator = base.GetEnumerator())
{
while (enumerator.MoveNext())
{
VirtualPathData virtualPath = enumerator.Current.GetVirtualPath(requestContext, values);
if (virtualPath != null)
{
virtualPath.VirtualPath = this.NormalizeVirtualPath(requestContext, virtualPath.VirtualPath);
return virtualPath;
}
}
}
}
return null;
} public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values)
{
requestContext = this.GetRequestContext(requestContext);
if (string.IsNullOrEmpty(name))
{
return this.GetVirtualPath(requestContext, values);
}
RouteBase routeBase;
bool flag;
using (this.GetReadLock())
{
flag = this._namedMap.TryGetValue(name, out routeBase);
}
if (!flag)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("RouteCollection_NameNotFound"), new object[]
{
name
}), "name");
}
VirtualPathData virtualPath = routeBase.GetVirtualPath(requestContext, values);
if (virtualPath != null)
{
virtualPath.VirtualPath = this.NormalizeVirtualPath(requestContext, virtualPath.VirtualPath);
return virtualPath;
}
return null;
} public IDisposable GetWriteLock()
{
this._rwLock.EnterWriteLock();
return new RouteCollection.WriteLockDisposable(this._rwLock);
} public void Ignore(string url)
{
this.Ignore(url, null);
} public void Ignore(string url, object constraints)
{
if (url == null)
{
throw new ArgumentNullException("url");
}
RouteCollection.IgnoreRouteInternal item = new RouteCollection.IgnoreRouteInternal(url)
{
Constraints = new RouteValueDictionary(constraints)
};
base.Add(item);
} protected override void InsertItem(int index, RouteBase item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
if (base.Contains(item))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, SR.GetString("RouteCollection_DuplicateEntry"), new object[]), "item");
}
base.InsertItem(index, item);
} protected override void RemoveItem(int index)
{
this.RemoveRouteName(index);
base.RemoveItem(index);
} private void RemoveRouteName(int index)
{
RouteBase routeBase = base[index];
foreach (KeyValuePair<string, RouteBase> current in this._namedMap)
{
if (current.Value == routeBase)
{
this._namedMap.Remove(current.Key);
break;
}
}
} protected override void SetItem(int index, RouteBase item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
if (base.Contains(item))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, SR.GetString("RouteCollection_DuplicateEntry"), new object[]), "item");
}
this.RemoveRouteName(index);
base.SetItem(index, item);
}
}
RouteCollection类型直接继承Collection<RouteBase>,这个关系很明显,他就是用于存放Route路由规则对象的,用于注册Route路由对象的方法就是MapPageRoute方法,基于篇幅,其他方法就不细说了,大家可以细看。
4、RouteHandler
到此,我们有了路由规则对象Route,也通过RouteTable的Routes属性注册好了,系统启动了,我们要截获请求,截获请求的类文件就是UrlRoutingModule,在上面我已经贴出该类的全部源码了,这里就不写了,截获请求后,开始和RouteTable里面的每一个Route对象进行比较,如果匹配就获得RouteData对象了,有了RouteData对象,其实我们是为了获得RouteHandler的值,有了他的值我们才可以继续,我说了这么多就是这个方法所实现的:
public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData == null)
{
return;
}
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[]));
}
if (routeHandler is StopRoutingHandler)
{
return;
}
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[]
{
routeHandler.GetType()
}));
}
if (!(httpHandler is UrlAuthFailureHandler))
{
context.RemapHandler(httpHandler);
return;
}
if (FormsAuthenticationModule.FormsAuthRequired)
{
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
return;
}
throw new HttpException(, SR.GetString("Assess_Denied_Description3"));
}
有了RouteData对象,在和HttpContext对象一起封装为RequestContext对象,
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext = requestContext;
然后我们根据RouteData对象的RouteHandler属性获取HttpHandler,我们调用GetHttpHandler方法的时候我们需要RequestContext类型作为参数,代码如下:
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
RouoteHandler属性表示的类型必须实现了如下接口:
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
最后,我们重新路由HttpHandler,在ASP.NET MVC终究是MvcHandler,他开始结果整个请求,进行Controlller激活和Action方法的执行。
context.RemapHandler(httpHandler);
四、结论
好了,做个总结吧,总体来说不是很难,只要大家理顺了,就好了,这些类的设计都是有因果关系的,理解这种因果关系,再把我前后顺序,理解起来就简单了。
今天就到这里,写的有点长,大家慢慢看,欢迎讨论,我要去赶火车了。
白话ASP.NET MVC之一:Url 路由的更多相关文章
- ASP.NET MVC 的URL路由介绍
在这个教程中,向你介绍每个ASP.NET MVC一个重要的特点叫做URL路由.URL路由模块是负责映射从浏览器请求到特定的控制器动作. 在教程的第一部分,你将学习标准路由表如何映射到控制器的动作.在教 ...
- 白话ASP.NET MVC之三:Controller是如何解析出来的
我们在上一篇文章中介绍Controller激活系统中所涉及到的一些类型,比如有关Controller类型的相关定义类型就包括了IController类型,IAsyncController类型,Cont ...
- 【翻译】ASP.NET MVC 5属性路由(转)
转载链接:http://www.cnblogs.com/thestartdream/p/4246533.html 原文链接:http://blogs.msdn.com/b/webdev/archive ...
- 为ASP.NET MVC应用添加自定义路由
这里,我们将学习如何给asp.net mvc应用添加自定义路由.用自定义路由来修改默认路由表. 对一些简单的asp.net mvc应用,默认的路由表就已经足够了.但是,当你需要创建特殊的路由时,就需要 ...
- 【转载】ASP.NET MVC重写URL制作伪静态网页,URL地址以.html结尾
在搜索引擎优化领域,静态网页对于SEO的优化有着很大的好处,因此很多人就想把自己的网站的一些网页做成伪静态.我们现在在网络上发现很多博客网站.论坛网站.CMS内容管理系统等都有使用伪静态这一种情况,伪 ...
- MVC的URL路由规则
MVC的URL路由规则 Routing的作用:它首先是获取到View传过来的请求,并解析Url请求中Controller和Action以及数据,其次他将识别出来的数据传递给Controller的Act ...
- Asp.Net MVC2.0 Url 路由入门---实例篇
本篇主要讲述Routing组件的作用,以及举几个实例来学习Asp.Net MVC2.0 Url路由技术. 接着上一篇开始讲,我们在Global.asax中注册一条路由后,我们的请求是怎么转到相应的Vi ...
- Asp.Net MVC2.0 Url 路由入门---实例篇 【转】
本篇主要讲述Routing组件的作用,以及举几个实例来学习Asp.Net MVC2.0 Url路由技术. 接着上一篇开始讲,我们在Global.asax中注册一条路由后,我们的请求是怎么转到相应的Vi ...
- ASP.NET MVC 学习之路由(URL Routing)
在ASP.NET MVC中,一个URL请求是由对应的一个Controller中的Action来处理的,由URL Routing来告诉MVC如何定位到正确的Controller和Action. 默认路由 ...
随机推荐
- 波浪号和Hyphen扩展
Bash将波浪号作为路径扩展符 $echo ~ //扩展为当前用户主目录的全路径名/home/user $echo ~user //扩展为用户user的主目录/home/user $echo ~+ / ...
- ubuntu 系统 更改屏幕亮度为最大(15级亮度)
历经千辛万苦终于搞定屏幕亮度,现将成果分享如下. 硬件:联想K29 系统:UBUNTU 14.04 一.执行命令 sudo gedit /etc/default/grub 二.更改文本 然后找到 GR ...
- LoadRunner11_录制Oracle数据库脚本
[oracle环境] ①oracle:无需在本地安装oracle,但是oracle的odbc驱动一定要装:(我的安装路径为 D:\oracle ).安装好后在环境变量 " Path &quo ...
- 一劳永逸的解决AFNetworking3.0网络请求问题(面向对象封装大法,block回调)
AFNetworking在iOS网络请求第三方库中占据着半壁江山,前段时间将AFNetworking进行了3.0版本的迁移,运用面向对象的设计将代码进行封装整合,这篇文章主要为还在寻找AFNetwor ...
- java中的注解总结
1. 什么是注解 注解是java5引入的特性,在代码中插入一种注释化的信息,用于对代码进行说明,可以对包.类.接口.字段.方法参数.局部变量等进行注解.注解也叫元数据(meta data).这些注解信 ...
- 积累一些不太常用的c语言知识(不断更新)
这里积累一些日常编程用得比较少的知识,不断添加. scanf("%c%*c%c",&a,&b); 其中的*表示跳过,本来输入三个数字,结果中间那个读入后被抛弃,a和 ...
- MYSQL数据类型和where条件
MySQL中常见的数据类型 一.字符型 ① CHAR(N):固定N个字符长度的字符串,如果长度不够自动空格补齐; N的范围 0~255 ② VARCHAR(N): 存储可变长度的字符串,最常用 ③ T ...
- eclispe JavaEE 配置tomcat
http://blog.csdn.net/hongshan50/article/details/8293526 http://blog.csdn.net/longshengguoji/article/ ...
- java 获得当前时间 年月日时分秒 星期几
<%SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");//设置日期格式SimpleDat ...
- apache 基本vhost配置
经常使用Apache虚拟主机进行开发和测试,但每次需要配置虚拟主机时都习惯性的ctrl+c和ctrl+v,这次由于重装系统,需要配置一个新的PHP开发环境虚拟主机,于是总结一下Apaceh配置http ...