ASP.NET Web API 框架研究 ASP.NET Web API 路由

时间:2023-03-08 17:01:20
ASP.NET Web API 框架研究 ASP.NET Web API 路由

  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”中,类及主要成员和相互关系如下图:

ASP.NET Web API 框架研究 ASP.NET Web API 路由

ASP.NET Web API 框架研究 ASP.NET Web API 路由

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”

  ASP.NET Web API 框架研究 ASP.NET Web API 路由

  ASP.NET Web API 框架研究 ASP.NET Web API 路由

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来注册,如下

  ASP.NET Web API 框架研究 ASP.NET Web API 路由

  其中要注意的是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("”);
  values.Add(");

  ");
  routeData.Values.Add(");

  //往HttpRequestMessage字典属性中设置HttpRouteData,其中数据优先级比较低,不使用
  request.SetRouteData(routeData);
  pathData = route.GetVirtualPath(request, values);

  最终结果是weather/021/3

  当然前边获取到变量值后,如key为areacode的值021,还要检测其是否满足所有约束

  最后才返回一个HttpVirtualPathData对象。