• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

ASP.NETWebAPI框架研究ASP.NET路由

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

 

  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
  {
    


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
ASP.NETMVC数据库依赖缓存发布时间:2022-07-10
下一篇:
ASP.NET验证码控件-AutoAuthCodev1.5发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap