ASP.NET Web API 如果采用Web Host方式来寄宿,在请求进入Web API 消息处理管道之前,就会用ASP.NET 自身的路由系统根据注册的路由表,解析出当前请求的HttpController和Action的名称,以及与目标Action方法某个参数进行绑定的路由变量。
ASP.NET路由系统包括两方面应用:
- 注册路由映射,即注册路由模板和物理文件的匹配关系,实现请求URL地址和处理请求的物理地址的分离,可以提高请求URL可读性,SEO优化,灵活性,即请求URL和处理请求的物理文件的变化互不影响
- URL生成,根据注册的路由规则生成相应URL,使用这种URL,刚好利用了路由注册的灵活性,可以使原来生成的URL不中断,只需要修改路由配置的处理文件。
一、涉及的类及源码分析
涉及的主要类型都在System.Web.Routing中,类及主要成员和相互关系如下图:
1、RouteBase
public abstract class RouteBase { private bool _routeExistingFiles = true; //根据路由模板与请求的URL进行匹配,如果成功返回RouteData,否则返回NULL public abstract RouteData GetRouteData(HttpContextBase httpContext); //采用指定的路由变量列表(包含名称和值)与URL路由模板进行匹配,若匹配成功,返回完整URL,否则返回NULL public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); //表示是否对现有的物理文件实施路由,默认值为true,即通过地址“/employees/hr/index.aspx” 是访问不到 Index.aspx页面 //但是有时候我们希望以物理文件路径方式来访问对应的物理文件,可以设置该值为false,就可以访问到Index.aspx页面 public bool RouteExistingFiles { get { return this._routeExistingFiles; } set { this._routeExistingFiles = value; } } }
2、Route
RouteBase唯一子类,基于路由模板模式的路由匹配规则就定义在其中,向全局路由表中添加的就是一个Route对象。
public class Route : RouteBase { private const string HttpMethodParameterName = "httpMethod"; private string _url; private ParsedRoute _parsedRoute;
//构造函数,前边省略N个重载 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; }
//为路由模板中的变量以正则表达式的形武设定一些约束条件,Key为变量名,Value为正则表达式 //如果有定义,匹配也要满足该约束 public RouteValueDictionary Constraints { get; set; } //存储额外的变量值 public RouteValueDictionary DataTokens { get; set; } //路由变量默认值,也不一定要定义在路由模板中 public RouteValueDictionary Defaults { get; set; }
public IRouteHandler RouteHandler { get; set; }
//路由模板,如/weather/{areacode}/{days},请求的URL就是跟该模板进行匹配,/分割成多个段,每个段又分变量(areacode,days)和字面量(weather) //匹配的两个条件,段数和路由模板相同,以及对应文本段内容也要相同,注,URL大小写不敏感 public string Url { get { return this._url ?? string.Empty; } set { this._parsedRoute = RouteParser.Parse(value); this._url = value; } }
//根据路由模板与请求的URL进行匹配,如果成功返回RouteData,否则返回NULL
public override RouteData GetRouteData(HttpContextBase httpContext) { RouteValueDictionary values = this._parsedRoute.Match(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo, this.Defaults); if (values == null) return (RouteData)null; RouteData routeData = new RouteData((RouteBase)this, this.RouteHandler); if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) return (RouteData)null; foreach (KeyValuePair<string, object> keyValuePair in values) routeData.Values.Add(keyValuePair.Key, keyValuePair.Value); if (this.DataTokens != null) { foreach (KeyValuePair<string, object> dataToken in this.DataTokens) routeData.DataTokens[dataToken.Key] = dataToken.Value; } return routeData; }
//采用指定的路由变量列表(包含名称和值)与URL路由模板进行匹配,若匹配成功,返回完整URL,否则返回NULL 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 (VirtualPathData)null; if (!this.ProcessConstraints(requestContext.HttpContext, boundUrl.Values, RouteDirection.UrlGeneration)) return (VirtualPathData)null; VirtualPathData virtualPathData = new VirtualPathData((RouteBase)this, boundUrl.Url); if (this.DataTokens != null) { foreach (KeyValuePair<string, object> dataToken in this.DataTokens) virtualPathData.DataTokens[dataToken.Key] = dataToken.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 str = constraint as string; if (str == null) throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.CurrentUICulture, System.Web.SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[2] { (object) parameterName, (object) this.Url })); object obj; values.TryGetValue(parameterName, out obj); return Regex.IsMatch(Convert.ToString(obj, (IFormatProvider)CultureInfo.InvariantCulture), "^(" + str + ")$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); }
private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection) { if (this.Constraints != null) { foreach (KeyValuePair<string, object> constraint in this.Constraints) { if (!this.ProcessConstraint(httpContext, constraint.Value, constraint.Key, values, routeDirection)) return false; } } return true; }
}
另外,RequestContext是对Http请求上下文和路由数据的封装
public class RequestContext { public RequestContext() { }
public RequestContext(HttpContextBase httpContext, RouteData routeData) { if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); if (routeData == null) throw new ArgumentNullException(nameof(routeData)); this.HttpContext = httpContext; this.RouteData = routeData; } //请求上下文 public virtual HttpContextBase HttpContext { get; set; } //路由数据 public virtual RouteData RouteData { get; set; } }
3、RouteData
RouteBase的GetRouteData方法的返回类型,用于封装解析后路由数据,其用RouteValueDictionary保存路由变量数据,RouteValueDictionary是—个字典,其 Key和 Value分别表示路由变量的名称和值,定义如下:
public class RouteValueDictionary : IDictionary<string, object>
{
}
public class RouteData { private RouteValueDictionary _values = new RouteValueDictionary(); private RouteValueDictionary _dataTokens = new RouteValueDictionary(); private IRouteHandler _routeHandler;
public RouteData() { }
public RouteData(RouteBase route, IRouteHandler routeHandler) { this.Route = route; this.RouteHandler = routeHandler; }
//是直接附加到Route上的自定义变量。 public RouteValueDictionary DataTokens { get { return this._dataTokens; } }
//解析它的 Route对象
public RouteBase Route { get; set; }
//提供最 终用 于处理请求的HttpHandIer对象(通过调用其GetHttpHandler方法获取)
//可以在构造函数中传入,也可以属性赋值 public IRouteHandler RouteHandler { get { return this._routeHandler; } set { this._routeHandler = value; } }
//其中的路由变量是Route通过对请求URL的解析得到的 public RouteValueDictionary Values { get { return this._values; } }
//获取包含某些固定名称的变量值(如controller和action)对应的值 public string GetRequiredString(string valueName) { object obj; if (this.Values.TryGetValue(valueName, out obj)) { string str = obj as string; if (!string.IsNullOrEmpty(str)) return str; } //不存在直接抛出异常 throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.CurrentUICulture, System.Web.SR.GetString("RouteData_RequiredValue"), new object[1] { (object) valueName })); } }
public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }
4、VirtualPathData
RouteBase的GeVirtualPathData方法的返回类型
public class VirtualPathData { private RouteValueDictionary _dataTokens = new RouteValueDictionary(); private string _virtualPath;
public VirtualPathData(RouteBase route, string virtualPath) { this.Route = route; this.VirtualPath = virtualPath; }
//来源于附加到 Route的 自定义变量集合 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; } } }
5、RouteTable Routes RouteCollection
全局路由表,即RouteTable类的静态属性Routes 类型为RouteCollection,通过其中的MapPageRoute方法进行路由映射
public class RouteTable { private static RouteCollection _instance = new RouteCollection();
public static RouteCollection Routes { get { return RouteTable._instance; } } }
RouteCollection 是Route的集合
主要逻辑:
RouteCollection的GetRouteData和GetVirtalPath方法会遍历集合中的每个Route对象,并传入给定的参数调用同名方法直到找到一个匹配的Route(返回值不为Null),并返回相应的RouteData和VirtaulPathData对象,如果集合中任何一个Route都不匹配,最终返回NULL
读写锁的应用:
RouteCollection这个集合对象不是线程安全的,使用ReaderWriterLockSlim进行线程读写同步,多个线程可以同时读,其他情况都不允许,一个线程写时,其他的线程不能读或写,一个线程在读时候,其他线程只能读,不能写,即多个线程只能读读,不能读写、写写;
在对集合进行读取或更新时候,会调用GetReadLock和GetWriteLock方法获取读锁或写锁,返回的是内嵌私有类型:ReadLockDispsoabled和WriteLockDispsoabled,他们实现了接口IDispsoabled,是对ReaderWriterLockSlim的读写锁功能的封装。
public class RouteCollection : Collection<RouteBase> { private Dictionary<string, RouteBase> _namedMap = new Dictionary<string, RouteBase>((IEqualityComparer<string>)StringComparer.OrdinalIgnoreCase); private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
public RouteCollection() { }
public bool RouteExistingFiles { get; set; }
public void Add(string name, RouteBase item) { //... this.Add(item); if (!string.IsNullOrEmpty(name)) this._namedMap[name] = item; Route route = item as Route; if (route == null || route.RouteHandler == null) return; }
//省略N个重载方法 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens) { if (routeUrl == null) throw new ArgumentNullException(nameof(routeUrl)); //新建路由对象,IRouteHandler直接new PageRouteHandler Route route = new Route(routeUrl, defaults, constraints, dataTokens, (IRouteHandler)new PageRouteHandler(physicalFile, checkPhysicalUrlAccess)); //添加进去 this.Add(routeName, (RouteBase)route); return route; }
protected override void ClearItems() { this._namedMap.Clear(); base.ClearItems(); }
public IDisposable GetReadLock() { this._rwLock.EnterReadLock(); return (IDisposable)new System.Web.Routing.RouteCollection.ReadLockDisposable(this._rwLock); }
private RequestContext GetRequestContext(RequestContext requestContext) { if (requestContext != null) return requestContext; HttpContext current = HttpContext.Current; if (current == null) throw new InvalidOperationException(System.Web.SR.GetString("RouteCollection_RequiresContext")); return new RequestContext((HttpContextBase)new HttpContextWrapper(current), new RouteData()); }
private bool IsRouteToExistingFile(HttpContextBase httpContext) { string executionFilePath = httpContext.Request.AppRelativeCurrentExecutionFilePath; if (!(executionFilePath != "~/") || this.VPP == null) return false; if (!this.VPP.FileExists(executionFilePath)) return this.VPP.DirectoryExists(executionFilePath); return true; }
public RouteData GetRouteData(HttpContextBase httpContext) { ... using (this.GetReadLock()) { //遍历集合中所有RouteBase,并调用其GetRouteData方法,找到了就马上返回 foreach (RouteBase routeBase in (Collection<RouteBase>)this) { RouteData routeData = routeBase.GetRouteData(httpContext); if (routeData != null) { if (!routeBase.RouteExistingFiles) { if (!flag2) flag1 = this.IsRouteToExistingFile(httpContext); if (flag1) return (RouteData)null; } return routeData; } } } return (RouteData)null; }
public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { requestContext = this.GetRequestContext(requestContext); using (this.GetReadLock()) { //遍历集合中所有RouteBase,并调用其GetVirtualPath方法,找到了就马上返回 foreach (RouteBase routeBase in (Collection<RouteBase>)this) { VirtualPathData virtualPath = routeBase.GetVirtualPath(requestContext, values); if (virtualPath != null) { virtualPath.VirtualPath = this.NormalizeVirtualPath(requestContext, virtualPath.VirtualPath); return virtualPath; } } } return (VirtualPathData)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) { VirtualPathData virtualPath = routeBase.GetVirtualPath(requestContext, values); if (virtualPath == null) return (VirtualPathData)null; virtualPath.VirtualPath = this.NormalizeVirtualPath(requestContext, virtualPath.VirtualPath); return virtualPath; } throw new ArgumentException(string.Format((IFormatProvider)CultureInfo.CurrentUICulture, System.Web.SR.GetString("RouteCollection_NameNotFound"), new object[1] { (object) name }), nameof(name)); }
public IDisposable GetWriteLock() { this._rwLock.EnterWriteLock(); return (IDisposable)new System.Web.Routing.RouteCollection.WriteLockDisposable(this._rwLock); }
//忽略路由 public void Ignore(string url) { this.Ignore(url, (object)null); }
public void Ignore(string url, object constraints) { if (url == null) throw new ArgumentNullException(nameof(url)); System.Web.Routing.RouteCollection.IgnoreRouteInternal ignoreRouteInternal = new System.Web.Routing.RouteCollection.IgnoreRouteInternal(url); RouteValueDictionary routeValueDictionary = new RouteValueDictionary(constraints); ignoreRouteInternal.Constraints = routeValueDictionary; this.Add((RouteBase)ignoreRouteInternal); }
protected override void InsertItem(int index, RouteBase item) { if (item == null) throw new ArgumentNullException(nameof(item)); if (this.Contains(item)) throw new ArgumentException(string.Format((IFormatProvider)CultureInfo.CurrentCulture, System.Web.SR.GetString("RouteCollection_DuplicateEntry"), new object[0]), nameof(item)); base.InsertItem(index, item); }
protected override void RemoveItem(int index) { this.RemoveRouteName(index); base.RemoveItem(index); }
private void RemoveRouteName(int index) { RouteBase routeBase = this[index]; foreach (KeyValuePair<string, RouteBase> named in this._namedMap) { if (named.Value == routeBase) { this._namedMap.Remove(named.Key); break; } } }
protected override void SetItem(int index, RouteBase item) { if (item == null) throw new ArgumentNullException(nameof(item)); if (this.Contains(item)) throw new ArgumentException(string.Format((IFormatProvider)CultureInfo.CurrentCulture, System.Web.SR.GetString("RouteCollection_DuplicateEntry"), new object[0]), nameof(item)); this.RemoveRouteName(index); base.SetItem(index, item); }
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, (IRouteHandler)new StopRoutingHandler()) { }
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues) { return (VirtualPathData)null; } } }
6、IRouteConstraint
除了用正则表达式对某个变量进行约束外,还可以用一个实现了IRouteConstraint接口的对象对整个请求进行约束
public interface IRouteConstraint {
|
请发表评论