WebAPI系列-路由注册

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

1. 引言

平时工作中我们在通过VS IDE工具创建了一个Web API项目,创建控制器以及后续部署之后,我们的浏览器或者客户端就可以根据URL来访问获取指定的数据了,那Web API是如何找到指定的资源呢?

其实在web API中是提前将一定格式规则预设到系统中,在浏览器或者客户端进行索取数据资源的时候,根据用户输入的URL来和预设的规则进行匹配,所以用户在输入URL时需要按照指定规则的格式来获取,而这个指定规则的格式就是web API的路由。

2.路由是如何在WebApi中注册的呢?

我这篇分享就是讲解Web API是如何将"指定规则"注册到框架的,官方对路由在代码中的定义是这样的:

[一个ASP.NET的Web应用具有一个全局的路由表,它是通过一个RouteTable类中类型为RouteCollection的Routes静态属性来表示的.]

public class RouteTable
{
    private static RouteCollection _instance = new RouteCollection();

    public static RouteCollection Routes => _instance;
}
复制代码

有人会好奇这里为何将Routes设为静态属性?

其实我觉得为静态的原因是常驻内存,可以在应用程序当前存在的生命周期中保持不会被释放,可以把 Routes属性想象为类似小型的数据库,而这个某种意义上的"数据库"存储的是框架中路由注册的信息。

3.如何解释RouteTable?

1.首先我们需要打开一个Web API应用程序来分析,我们知道在aspnet中web应用程序的启动项目是Global类,路由注册就存在Global类的Application_Start方法中。

public class WebApiApplication : HttpApplication
{
    protected void Application_Start()
    {
       GlobalConfiguration.Configure(WebApiConfig.Register);
    }
}
复制代码

2.在上面代码中我去掉了其他的一些代码,我们在web应用程序启动时,首先调用GlobalConfiguration.Configure(WebApiConfig.Register)方法,这个方法接收的参数是一个Action</HttpConfiguration/> 作为参数,看到Action我们的第一反应这里应该是作为回调,在内部执行时将HttpConfiguration参数传递到外部,给我们预留了扩展空间.

3.我们找到WebApiConfig.Register的代码

 public static class WebApiConfig
 {
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute(
             name: "DefaultApi",
             routeTemplate: "api/{controller}/{id}",
             defaults: new { id = RouteParameter.Optional }
         );
     }
 }
复制代码
4.解读框架添加路由本质

我们看到Register方法中,框架加入路由调用的是config.Routes.MapHttpRoute方法

这里需要分开2步去解读它:

1.config.Routes是什么?

我们要查看HttpConfiguration源码中config.Routes是什么?

  1. config.Routes的定义,它取值为内部私有变量 _routes
public HttpRouteCollection Routes
{
    get { return _routes; }
}
复制代码
  1. _routes对应 HttpConfiguration构造时传入的HttpRouteCollection类型
//在HttpConfiguration构造时传入了一个HttpRouteCollection类型的参数赋值给_routes
public HttpConfiguration(HttpRouteCollection routes)
{
    _routes = routes;
}
复制代码
  1. 在GlobalConfiguration中找到HttpConfiguration初始化代码,找到HttpRouteCollection对应的实参是一个HostedHttpRouteCollection类型
  2. HostedHttpRouteCollection还接收一个RouteTable.Routes参数
//传入的HttpRouteCollection是一个接收RouteTable.Routes参数的
private static Lazy<HttpConfiguration> CreateConfiguration()
{
    return new Lazy<HttpConfiguration>(() =>
    {
      HttpConfiguration config = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes));    
      return config;
    });
}


复制代码

结论1

1.config.Routes == new  HostedHttpRouteCollection(RouteTable.Routes);
2.传入的参数RouteTable.Routes就是web应用程序中的全局的路由表.
复制代码
2.MapHttpRoute具体做了什么?
//转到MapHttpRoute源码,发现调用的是 routes.CreateRoute来创建一个IHttpRoute实例
//然后使用routes.Add来加入路由
public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, HttpMessageHandler handler)
{
    HttpRouteValueDictionary defaults2 = new HttpRouteValueDictionary(defaults);
    HttpRouteValueDictionary constraints2 = new HttpRouteValueDictionary(constraints);
    IHttpRoute httpRoute = routes.CreateRoute(routeTemplate, defaults2, constraints2, null, handler);
    routes.Add(name, httpRoute);
    return httpRoute;
}
复制代码

结论2

1.IHttpRoute实例是由HostedHttpRouteCollection中的CreateRoute来完成的;
2.routes.Add最终是调用RouteTable.Routes来将名称和路由一一对应添加到自身绑定的.
复制代码
5.简单的示意图

image.png