ASP.NET Web API 核心框架是一个独立的、抽象的消息处理管道,ASP.NET Web API有自己独立的路由系统,是消息处理管道的组成部分,其与ASP.NET路由系统有类似的设计,都能找到对应的类,虽然有一定区别,基本都是面向接口的,而且命名都以Http开始的,但是,其主要辑基本都一样。看这篇之前先看上一篇《ASP.NET Web API 框架研究 ASP.NET 路由》
一、涉及的类及源码分析
涉及的主要类型都在程序集“System.Net.Http.dll”和“System.Web.Http.dll”中,类及主要成员和相互关系如下图:
1、HttpRequestMessage HttpResponseMessage HttpContent
ASP.NET中请求和响应分别用HttpRequest和HttpResponse表示,直接从当前请求上下文HttpContext获取,而ASP.NET Web API通过HttpRequestMessage和HttpResponseMessage来表示,与HttpContext无关,且他们都有一个HttpContent属性,用来表示请求和响应报文的主体内容;这几个类在程序集“System.Net.Http.dll”中。
public class HttpRequestMessage : IDisposable
{
//多个重载的构造函数,注意参数HttpMethod 和requestUri
public HttpRequestMessage();
public HttpRequestMessage(HttpMethod method, Uri requestUri);
public HttpRequestMessage(HttpMethod method, string requestUri);
//请求主对象体内容
public HttpContent Content { get; set; }
//请求报头集合
public HttpRequestHeaders Headers { get; }
//起始行的请求方法,默认值GET方法
public HttpMethod Method { get; set; }
//起始行的请求URi
public Uri RequestUri { get; set; }
//起始行的请求Http协议版本,默认值HTTP 1.1
public Version Version { get; set; }
//字典属性,可以将任意对象附加进来,主要在请求过程不同阶段传递数据
public IDictionary<string, object> Properties { get; }
//Dispose模式,销毁非托管资源
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
//子类逻辑可以重写资源回收
protected virtual void Dispose(bool disposing)
{
if (!disposing || this.disposed)
return;
this.disposed = true;
if (this.content == null)
return;
this.content.Dispose();
}
public override string ToString();
}
//无字典属性Properties
public class HttpResponseMessage : IDisposable
{
//构造函数
public HttpResponseMessage();
public HttpResponseMessage(HttpStatusCode statusCode);
//响应主体内容
public HttpContent Content { get; set; }
//响应报头头集合
public HttpResponseHeaders Headers { get; }
//响应是否成功,响应码200-299之间
public bool IsSuccessStatusCode { get; }
//起始行的状态文字描述
public string ReasonPhrase { get; set; }
//对应的请求消息
public HttpRequestMessage RequestMessage { get; set; }
//起始行的响应码
public HttpStatusCode StatusCode { get; set; }
//起始行的HTTP版本
public Version Version { get; set; }
public void Dispose();
public HttpResponseMessage EnsureSuccessStatusCode();
public override string ToString();
protected virtual void Dispose(bool disposing);
}
public abstract class HttpContent : IDisposable
{
protected HttpContent();
//跟主体内容相关的请求或响应头集合
public HttpContentHeaders Headers { get; }
//将HTTP内容序列化为字节流
public Task CopyToAsync(Stream stream);
//将HTTP内容序列化到内存缓冲区
public Task LoadIntoBufferAsync();
//将HTTP内容序列化到字节数组
public Task<byte[]> ReadAsByteArrayAsync();
public Task<Stream> ReadAsStreamAsync();
//将HTTP内容序列化为字符串
public Task<string> ReadAsStringAsync();
public void Dispose();
protected virtual void Dispose(bool disposing);
}
HttpContent是抽象类,默认有很多子类继承它,表示不同的主体内容表示形式,如ByteArrayContent就是字节数组表示的请求或响应主体内容,另外,还有如ObjectContent,将对象的序列化结果作为请求或响应主体内容,HttpMessageContent将整个报文内容(主体、报头集合、主体内容)作为主体内容,这种方式的媒体类型即Content-Type为“application/http,msgtype=request”或“application/http,msgtype=response”
2、HttpRoute IHttpRoute
HttpRoute为ASP.NET Web API路由对象,所有的路由对象都继承IHttpRoute接口,不像ASP.NET路由系统中路由对象Route都继承抽象类RouteBase,而且成员还有一定区别。
public interface IHttpRoute
{
//路由模板,ASP.NET路由中是URL
string RouteTemplate { get; }
//默认值
IDictionary<string, object> Defaults { get; }
//约束
IDictionary<string, object> Constraints { get; }
//附加变量
IDictionary<string, object> DataTokens { get; }
//ASP.NET Web API核心类型,消息处理管道就是由一组HttpMessageHandler,ASP.NET路由中是IRouteHandler
HttpMessageHandler Handler { get; }
//两大功能之一,解析请求根据路由模板进行匹配获得路由数据
IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request);
//两大功能之一,根据提供的路由变量,根据路由模板生成URL,供应用使用
IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values);
}
以下是HttpRoute主要源代码,可作为写代码参考
public class HttpRoute : IHttpRoute
{
public static readonly string HttpRouteKey = "httproute";
internal const string RoutingContextKey = "MS_RoutingContext";
private string _routeTemplate;
private HttpRouteValueDictionary _defaults;
private HttpRouteValueDictionary _constraints;
private HttpRouteValueDictionary _dataTokens;
public HttpRoute()
: this(routeTemplate: null, defaults: null, constraints: null, dataTokens: null, handler: null, parsedRoute: null)
{
}
//省略N个重载构造函数
internal HttpRoute(string routeTemplate, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, HttpRouteValueDictionary dataTokens, HttpMessageHandler handler, HttpParsedRoute parsedRoute)
{
_routeTemplate = routeTemplate == null ? String.Empty : routeTemplate;
_defaults = defaults ?? new HttpRouteValueDictionary();
_constraints = constraints ?? new HttpRouteValueDictionary();
_dataTokens = dataTokens ?? new HttpRouteValueDictionary();
Handler = handler;
if (parsedRoute == null)
{
ParsedRoute = RouteParser.Parse(routeTemplate);
}
else
{
ParsedRoute = parsedRoute;
}
}
public IDictionary<string, object> Defaults
{
get { return _defaults; }
}
//省略Constraints,DataTokens,RouteTemplate
public HttpMessageHandler Handler { get; private set; }
internal HttpParsedRoute ParsedRoute { get; private set; }
//匹配路由
public virtual IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request)
{
if (virtualPathRoot == null)
{
throw Error.ArgumentNull("virtualPathRoot");
}
if (request == null)
{
throw Error.ArgumentNull("request");
}
RoutingContext context = GetOrCreateRoutingContext(virtualPathRoot, request);
if (!context.IsValid)
{
return null;
}
//1.匹配模板路由,调用ParsedRoute.Match
HttpRouteValueDictionary values = ParsedRoute.Match(context, _defaults);
if (values == null)
{
// 返回空说明没有匹配
return null;
}
// 验证所有约束
if (!ProcessConstraints(request, values, HttpRouteDirection.UriResolution))
{
return null;
}
return new HttpRouteData(this, values);
}
private static RoutingContext GetOrCreateRoutingContext(string virtualPathRoot, HttpRequestMessage request)
{
RoutingContext context;
if (!request.Properties.TryGetValue<RoutingContext>(RoutingContextKey, out context))
{
context = CreateRoutingContext(virtualPathRoot, request);
//将路由上下文数据放到请求的字段属性中,包含virtualPathRoot
request.Properties[RoutingContextKey] = context;
}
return context;
}
//生成URL
public virtual IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
//参数中路由变量不包含httproute的话,直接返回null,一个隐含条件参数字典中一定要包含httproute的key
if (values != null && !values.Keys.Contains(HttpRouteKey, StringComparer.OrdinalIgnoreCase))
{
return null;
}
// 验证包含httproute后,就把其重路由变量字典中去除掉
var newValues = GetRouteDictionaryWithoutHttpRouteKey(values);
IHttpRouteData routeData = request.GetRouteData();
IDictionary<string, object> requestValues = routeData == null ? null : routeData.Values;
//通过传进来的参数,以及HttpMessageRequest中的路由数据,以及HttpRoute本身的默认值,按这个优先级,解析生成URL
BoundRouteTemplate result = ParsedRoute.Bind(requestValues, newValues, _defaults, _constraints);
if (result == null)
{
return null;
}
//验证路由匹配约束
if (!ProcessConstraints(request, result.Values, HttpRouteDirection.UriGeneration))
{
return null;
}
return new HttpVirtualPathData(this, result.BoundTemplate);
}
//从路由变量表里去除httproute变量
private static IDictionary<string, object> GetRouteDictionaryWithoutHttpRouteKey(IDictionary<string, object> routeValues)
{
var newRouteValues = new HttpRouteValueDictionary();
if (routeValues != null)
{
foreach (var routeValue in routeValues)
{
if (!String.Equals(routeValue.Key, HttpRouteKey, StringComparison.OrdinalIgnoreCase))
{
newRouteValues.Add(routeValue.Key, routeValue.Value);
}
}
}
return newRouteValues;
}
//检查单个约束字典中的约束,约束是针对某个变量的
protected virtual bool ProcessConstraint(HttpRequestMessage request, object constraint, string parameterName, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
{
//先检查是不是继承自IHttpRouteConstraint约束类型
IHttpRouteConstraint customConstraint = constraint as IHttpRouteConstraint;
if (customConstraint != null)
{
//是的话就调用自身的Match方法验证
return customConstraint.Match(request, this, parameterName, values, routeDirection);
}
// 没有自定义约束就用正则表达式字符串匹配
string constraintsRule = constraint as string;
if (constraintsRule == null)
{
throw Error.InvalidOperation(SRResources.Route_ValidationMustBeStringOrCustomConstraint, parameterName, RouteTemplate, typeof(IHttpRouteConstraint).Name);
}
//验证正则表达式
object parameterValue;
values.TryGetValue(parameterName, out parameterValue);
string parameterValueString = Convert.ToString(parameterValue, CultureInfo.InvariantCulture);
string constraintsRegEx = "^(" + constraintsRule + ")$";
return Regex.IsMatch(parameterValueString, constraintsRegEx, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
//检查约束字典中的每个约束
private bool ProcessConstraints(HttpRequestMessage request, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
{
if (Constraints != null)
{
//遍历每个约束,全部通过了,才算通过
foreach (KeyValuePair<string, object> constraintsItem in Constraints)
{
if (!ProcessConstraint(request, constraintsItem.Value, constraintsItem.Key, values, routeDirection))
{
return false;
}
}
}
return true;
}
internal static void ValidateConstraint(string routeTemplate, string name, object constraint)
{
if (constraint is IHttpRouteConstraint)
{
return;
}
if (constraint is string)
{
return;
}
throw CreateInvalidConstraintTypeException(routeTemplate, name);
}
private static Exception CreateInvalidConstraintTypeException(string routeTemplate, string name)
{
return Error.InvalidOperation(
SRResources.Route_ValidationMustBeStringOrCustomConstraint,
name,
routeTemplate,
typeof(IHttpRouteConstraint).FullName);
}
}
3、HttpRouteData IHttpRouteData HttpRouteValueDictionary
HttpRouteData用来封装解析请求匹配路由模板并满足所有约束条件后的路由数据,其继承自IHttpRouteData ,与ASP.NET路由的RouteData相比成员更简单了
public interface IHttpRouteData
{
//生成该数据的 IHttpRoute
IHttpRoute Route { get; }
//解析出来的路由变量
IDictionary<string, object> Values { get; }
}
public class HttpRouteData : IHttpRouteData
{
private IHttpRoute _route;
private IDictionary<string, object> _values;
public HttpRouteData(IHttpRoute route)
: this(route, new HttpRouteValueDictionary())
{
}
public HttpRouteData(IHttpRoute route, HttpRouteValueDictionary values)
{
if (route == null)
{
throw Error.ArgumentNull("route");
}
if (values == null)
{
throw Error.ArgumentNull("values");
}
_route = route;
_values = values;
}
public IHttpRoute Route
{
get { return _route; }
}
public IDictionary<string, object> Values
{
get { return _values; }
}
}
public class HttpRouteValueDictionary : Dictionary<string, object>
{
public HttpRouteValueDictionary()
: base(StringComparer.OrdinalIgnoreCase)
{
}
public HttpRouteValueDictionary(IDictionary<string, object> dictionary)
: base(StringComparer.OrdinalIgnoreCase)
{
if (dictionary != null)
{
foreach (KeyValuePair<string, object> current in dictionary)
{
Add(current.Key, current.Value);
}
}
}
public HttpRouteValueDictionary(object values)
: base(StringComparer.OrdinalIgnoreCase)
{
IDictionary<string, object> valuesAsDictionary = values as IDictionary<string, object>;
if (valuesAsDictionary != null)
{
foreach (KeyValuePair<string, object> current in valuesAsDictionary)
{
Add(current.Key, current.Value);
}
}
else if (values != null)
{
foreach (PropertyHelper property in PropertyHelper.GetProperties(values))
{
Add(property.Name, property.GetValue(values));
}
}
}
}
4、HttpVirtualPathData IHttpVirtualPathData
调用GetVirtaulPath方法的返回值,比ASP.NET 路由中的VirtualPathData少个DataTokens,
public interface IHttpVirtualPathData
{
IHttpRoute Route { get; }
//生成的URL
string VirtualPath { get; set; }
}
public class HttpVirtualPathData : IHttpVirtualPathData
{
private string _virtualPath;
public HttpVirtualPathData(IHttpRoute route, string virtualPath)
{
if (route == null)
{
throw Error.ArgumentNull("route");
}
if (virtualPath == null)
{
throw Error.ArgumentNull("virtualPath");
}
Route = route;
VirtualPath = virtualPath;
}
public IHttpRoute Route { get; private set; }
public string VirtualPath
{
get { return _virtualPath; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_virtualPath = value;
}
}
}
5、 IHttpRouteConstraint HttpRouteDirection
除了正则表达式的约束外,可以自定义针对某个路由变量的约束,对应ASP.NET路由中的 IRouteConstraint
解析出路由数据后,还要验证请求是否满足所有约束条件
public interface IHttpRouteConstraint
{
/// </summary>
/// <param name="request">被验证的请求</param>
/// <param name="route">约束本身所在的路由对象</param>
/// <param name="parameterName">由于约束通常是针对某个路由变量的,这个就是路由变量</param>
/// <param name="values">是之前通过URL模式匹配得到的所有路由变量值</param>
/// <param name="routeDirection">路由方向,这个约束可以用在路由匹配上,也可以用在生成URL上</param>
bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection);
}
//路由方向,一个进,一个出,用在不同场景
public enum HttpRouteDirection
{
//利用路由模板匹配请求进而得到路由数据
UriResolution = 0,
//根据路由规则和路由变量生成URL
UriGeneration
}
ASP.NET WEB API框架定义了很多实现,如BoolRouteConstraint 验证解析出来的某个路由变量是否是一个bool类型,又如HttpMethodConstraint约束请求方法
public class BoolRouteConstraint : IHttpRouteConstraint
{
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (parameterName == null)
{
throw Error.ArgumentNull("parameterName");
}
if (values == null)
{
throw Error.ArgumentNull("values");
}
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
if (value is bool)
{
return true;
}
bool result;
string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
return Boolean.TryParse(valueString, out result);
}
return false;
}
}
public class HttpMethodConstraint : IHttpRouteConstraint
{
public HttpMethodConstraint(params HttpMethod[] allowedMethods)
{
if (allowedMethods == null)
{
throw Error.ArgumentNull("allowedMethods");
}
AllowedMethods = new Collection<HttpMethod>(allowedMethods);
}
public Collection<HttpMethod> AllowedMethods { get; private set; }
protected virtual bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (route == null)
{
throw Error.ArgumentNull("route");
}
if (parameterName == null)
{
throw Error.ArgumentNull("parameterName");
}
if (values == null)
{
throw Error.ArgumentNull("values");
}
switch (routeDirection)
{
case HttpRouteDirection.UriResolution:
return AllowedMethods.Contains(request.Method);
case HttpRouteDirection.UriGeneration:
HttpMethod constraint;
if (!values.TryGetValue(parameterName, out constraint))
{
return true;
}
return AllowedMethods.Contains(constraint);
default:
throw Error.InvalidEnumArgument(String.Empty, (int)routeDirection, typeof(HttpRouteDirection));
}
}
bool IHttpRouteConstraint.Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
return Match(request, route, parameterName, values, routeDirection);
}
}
6、HttpRouteCollection
同ASP.NET路由中的全局路由表类型RouteCollection,但是创建路由是方法CreateRoute方法,同时也有对应GetVirtualPath和GetRouteData方法,逻辑差不多按顺序调用每个HttpRoute对象的同名方法,匹配或生成成功就马上返回,否则返回NULL,见下边源码
public class HttpRouteCollection : ICollection<IHttpRoute>, IDisposable
{
private static readonly Uri _referenceBaseAddress = new Uri("http://localhost");
private readonly string _virtualPathRoot;
private readonly List<IHttpRoute> _collection = new List<IHttpRoute>();
private readonly IDictionary<string, IHttpRoute> _dictionary = new Dictionary<string, IHttpRoute>(StringComparer.OrdinalIgnoreCase);
private bool _disposed;
public HttpRouteCollection()
: this("/")
{
}
public HttpRouteCollection(string virtualPathRoot)
{
if (virtualPathRoot == null)
{
throw Error.ArgumentNull("virtualPathRoot");
}
Uri address = new Uri(_referenceBaseAddress, virtualPathRoot);
_virtualPathRoot = "/" + address.GetComponents(UriComponents.Path, UriFormat.Unescaped);
}
//按顺序遍历路由集合中的每个路由对应的方法,来获取路由解析数据
public virtual IHttpRouteData GetRouteData(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
//循环遍历每个HttpRoute
for (int i = 0; i < _collection.Count; i++)
{
//virtualPathRoot 可能存放在HttpRequestMessage中的字典属性中,如果存在就直接使用,否则使用默认的,virtualPathRoot 的影响见后边路由解析
string virtualPathRoot = GetVirtualPathRoot(request.GetRequestContext());
IHttpRouteData routeData = _collection[i].GetRouteData(virtualPathRoot, request);
if (routeData != null)
{
return routeData;
}
}
return null;
}
//利用某个路由名称(name)指定的路由,以及路由字典数据,以及请求HttpRequestMessage ,生成URL
public virtual IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, string name, IDictionary<string, object> values)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (name == null)
{
throw Error.ArgumentNull("name");
}
//根据路由名称从路由表中获取某个路由
IHttpRoute route;
if (!_dictionary.TryGetValue(name, out route))
{
throw Error.Argument("name", SRResources.RouteCollection_NameNotFound, name);
}
//再用该路由调用GetVirtualPath方法获得HttpVirtualPathData
IHttpVirtualPathData virtualPath = route.GetVirtualPath(request, values);
if (virtualPath == null)
{
return null;
}
//virtualPathRoot 可能存放在HttpRequestMessage中的字典属性中,如果存在就直接使用,否则使用默认的
string virtualPathRoot = GetVirtualPathRoot(request.GetRequestContext());
if (!virtualPathRoot.EndsWith("/", StringComparison.Ordinal))
{
virtualPathRoot += "/";
}
//最终URL要不跟路径和解析出来的URL拼装起来
return new HttpVirtualPathData(virtualPath.Route, virtualPathRoot + virtualPath.VirtualPath);
}
private string GetVirtualPathRoot(HttpRequestContext requestContext)
{
if (requestContext != null)
{
return requestContext.VirtualPathRoot ?? String.Empty;
}
return _virtualPathRoot;
}
public virtual IHttpRoute CreateRoute(string routeTemplate, IDictionary<string, object> defaults, IDictionary<string, object> constraints, IDictionary<string, object> dataTokens, HttpMessageHandler handler)
{
HttpRouteValueDictionary routeDefaults = new HttpRouteValueDictionary(defaults);
HttpRouteValueDictionary routeConstraints = new HttpRouteValueDictionary(constraints);
HttpRouteValueDictionary routeDataTokens = new HttpRouteValueDictionary(dataTokens);
foreach (var constraint in routeConstraints)
{
ValidateConstraint(routeTemplate, constraint.Key, constraint.Value);
}
return new HttpRoute(routeTemplate, routeDefaults, routeConstraints, routeDataTokens, handler);
}
protected virtual void ValidateConstraint(string routeTemplate, string name, object constraint)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
if (constraint == null)
{
throw Error.ArgumentNull("constraint");
}
HttpRoute.ValidateConstraint(routeTemplate, name, constraint);
}
public virtual void Add(string name, IHttpRoute route)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
if (route == null)
{
throw Error.ArgumentNull("route");
}
_dictionary.Add(name, route);
_collection.Add(route);
}
public virtual void Clear()
{
_dictionary.Clear();
_collection.Clear();
}
public virtual bool ContainsKey(string name)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
return _dictionary.ContainsKey(name);
}
public virtual void Insert(int index, string name, IHttpRoute value)
{
if (name == null)
{
throw Error.ArgumentNull("name");
}
if (value == null)
{
throw Error.ArgumentNull("value");
}
if (_collection[index] != null)
{
_dictionary.Add(name, value);
_collection.Insert(index, value);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
HashSet<IDisposable> handlers = new HashSet<IDisposable>();
foreach (var route in this)
{
if (route.Handler != null)
{
handlers.Add(route.Handler);
}
}
foreach (var handler in handlers)
{
handler.Dispose();
}
}
_disposed = true;
}
}
}
7、HttpConfiguration HttpRouteCollection
HttpConfiguration用来完成ASP.NET Web API的所有基本配置,是针对整个应用的全局配置,路由表就放在其中,是一个只读属性Routes,类型为HttpRouteCollection,不像ASP.NET路由表是放在类RouteTable的静态属性Routes里,另外,值得一提的是Properties,类似HttpRequestMessage同名字典属性,可以往里添加任何对象,这里可供全局应用使用;另外,我们注册和忽略路由定义了一个扩展方法来实现,间接调用路由表里方法,这个包含扩展方法的类就是HttpRouteCollectionExtensions,不像ASP.NET路由直接调用Routes方法。
public class HttpConfiguration : IDisposable
{
private readonly HttpRouteCollection _routes;
private readonly ConcurrentDictionary<object, object> _properties = new ConcurrentDictionary<object, object>();
private readonly MediaTypeFormatterCollection _formatters;
private readonly Collection<DelegatingHandler> _messageHandlers = new Collection<DelegatingHandler>();
private readonly HttpFilterCollection _filters = new HttpFilterCollection();
private IDependencyResolver _dependencyResolver = EmptyResolver.Instance;
private Action<HttpConfiguration> _initializer = DefaultInitializer;
private bool _initialized;
private bool _disposed;
public HttpRouteCollection Routes { get; }
//...
}
public static class HttpRouteCollectionExtensions
{
//..省略N个重载,注册路由映射
public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, HttpMessageHandler handler)
{
if (routes == null)
{
throw Error.ArgumentNull("routes");
}
HttpRouteValueDictionary defaultsDictionary = new HttpRouteValueDictionary(defaults);
HttpRouteValueDictionary constraintsDictionary = new HttpRouteValueDictionary(constraints);
//先创建建路由
IHttpRoute route = routes.CreateRoute(routeTemplate, defaultsDictionary, constraintsDictionary, dataTokens: null, handler: handler);
//再往路由表里添加路由
routes.Add(name, route);
return route;
}
public static IHttpRoute IgnoreRoute(this HttpRouteCollection routes, string routeName, string routeTemplate)
{
return IgnoreRoute(routes, routeName, routeTemplate, constraints: null);
}
//注册忽略路由
public static IHttpRoute IgnoreRoute(this HttpRouteCollection routes, string routeName, string routeTemplate, object constraints)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (routeName == null)
{
throw new ArgumentNullException("routeName");
}
if (routeTemplate == null)
{
throw new ArgumentNullException("routeTemplate");
}
IgnoreHttpRouteInternal route = new IgnoreHttpRouteInternal(routeTemplate, new HttpRouteValueDictionary(constraints), new StopRoutingHandler());
routes.Add(routeName, route);
return route;
}
private sealed class IgnoreHttpRouteInternal : HttpRoute
{
public IgnoreHttpRouteInternal(string routeTemplate, HttpRouteValueDictionary constraints, HttpMessageHandler handler)
: base(routeTemplate, constraints: constraints, handler: handler, dataTokens: null, defaults: null)
{
}
public override IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
{
return null;
}
}
}
二、注册路由映射
在全局配置对象HttpConfiguration的路由表Routes里添加一个HttpRoute对象,利用HttpRouteCollection(Routes的类型)的扩展方法MapHttpRoute来注册,如下
其中要注意的是defaults为默认值,id设置成了RouteParameter.Optional意思可缺省,即如果请求URL没有提供对应Id,则解析结果就是没有Key为id的变量,如果是id设置成1,解析结果就是Key 为id,值为默认值1。
具体内部逻辑可以看前边相关源码及注释
三、路由匹配
即调用HttpRoute对象的GetRouteData方法的逻辑,首先,请求URL要符合路由模板模式,模式匹配后把路由变量保存到字典对象中,其次,请求要满足所有约束,最后,通过前两者后生成HttpRouteData对象。
另外,GetRouteData方法有个参数virtualPathRoot,默认值为“/” ,会影响解析结果,举个例子,
路由模板:“books/{name}/{title}/{id}”
请求URL:http://www.myhost/api/books/a/b/1
virtualPathRoot参数值“/”和/api/,前者返回NULL,后者成功解析
其实解析时候,先会把virtualPathRoot从请求URL中去除
约束是针对某个路由变量的,其保存在HttpRoute的Constraints属性中,类型为IDictionary<string, object>,Key为某个路由变量,Value可以是某个HttpRouteConstraint对象或其字符串形式。
四、生成URL
即调用HttpRoute对象的GetVirtualPath方法的逻辑,即通过路由模板和指定的路由变量生成一个URL。
首先,把路由模板中的变量占位符如{area}用路由变量值替换掉,路由变量值可以通过GetVirtualPath直接传入,也可以用HttpRequestMessage字典属性中的HttpRouteData值,或HttpRoute中定义的默认值,三者优先级从高到低,举个例子,
路由模板:weather/{areacode}/{day}
//values是参数传入,优先级最高,有限使用
values.Add("httproute", true);
values.Add("areacode", “021”);
values.Add("days", 3);
IHttpRouteData routeData = new HttpRouteData(route);
routeData.Values.Add("areacode", "0512");
routeData.Values.Add("days", 4);
//往HttpRequestMessage字典属性中设置HttpRouteData,其中数据优先级比较低,不使用
request.SetRouteData(routeData);
pathData = route.GetVirtualPath(request, values);
最终结果是weather/021/3
当然前边获取到变量值后,如key为areacode的值021,还要检测其是否满足所有约束
最后才返回一个HttpVirtualPathData对象。