为了减少由于单个请求挂掉而拖垮整站的情况发生,给所有请求做统计是一个不错的解决方法,通过观察哪些请求的耗时比较长,我们就可以找到对应的接口、代码、数据表,做有针对性的优化可以提高效率。在 asp.net web api 中我们可以通过注册一个 DelegatingHandler 来实现该功能。那在 asp.net core 中该如何实现呢?

一:比较 asp.net web api 和 asp.net core 的请求管道

 

观察这两张图,可以发现他们非常的相似,都是管道式的设计,在 asp.net web api 中,我们可以注册一系列的 DelegatingHandler 来处理请求上下文 HttpRequestMessage,在 asp.net core 中,我们可以注册一系列中间件来处理请求上下文,他们两者从功能和意义上是非常相似的,我这里这里不会详细介绍各自的管道是如何的(这样的文章非常多,博客园随处可见),他们都完成了类似中间件的功能,只是在代码设计上有一点区别。

我们先看一段代码,新建一个 asp.net web api 项目,添加几个 DelegatinHandler

然后在 Global 中注册

public class DelegatingHandler1 : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Trace.WriteLine("DelegatingHandler1 HashCode: " + this.GetHashCode()); Trace.WriteLine("DelegatingHandler1 base InnerHandler HashCode: " + base.InnerHandler.GetHashCode()); Trace.WriteLine("DelegatingHandler1 start"); var response = await base.SendAsync(request, cancellationToken); Trace.WriteLine("DelegatingHandler1 end"); return response; } } public class DelegatingHandler2 : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Trace.WriteLine("DelegatingHandler2 HashCode: " + this.GetHashCode()); Trace.WriteLine("DelegatingHandler2 base InnerHandler HashCode: " + base.InnerHandler.GetHashCode()); Trace.WriteLine("DelegatingHandler2 start"); var response = await base.SendAsync(request, cancellationToken); Trace.WriteLine("DelegatingHandler2 end"); return response; } } public class DelegatingHandler3 : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Trace.WriteLine("DelegatingHandler3 HashCode: " + this.GetHashCode()); Trace.WriteLine("DelegatingHandler3 base InnerHandler HashCode: " + base.InnerHandler.GetHashCode()); Trace.WriteLine("DelegatingHandler3 start"); var response = await base.SendAsync(request, cancellationToken); Trace.WriteLine("DelegatingHandler3 end"); return response; } }

修改一下 ValuesController

public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); GlobalConfiguration.Configuration.MessageHandlers.Add(new DelegatingHandler1()); GlobalConfiguration.Configuration.MessageHandlers.Add(new DelegatingHandler2()); GlobalConfiguration.Configuration.MessageHandlers.Add(new DelegatingHandler3()); } }

启动后输入路径 /api/values,我们可以在VS 的输出栏看到下面这些内容

public class ValuesController : ApiController { // GET api/values public IEnumerable<string> Get() { Trace.WriteLine("/api/values"); var handlers = this.RequestContext.Configuration.MessageHandlers; return new string[] { "value1", "value2" }; } }

启动后输入路径 /api/values,我们可以在VS 的输出栏看到下面这些内容

DelegatingHandler1 HashCode: 58154627
DelegatingHandler1 base InnerHandler HashCode: 35529478
DelegatingHandler1 start
DelegatingHandler2 HashCode: 35529478
DelegatingHandler2 base InnerHandler HashCode: 47422476
DelegatingHandler2 start
DelegatingHandler3 HashCode: 47422476
DelegatingHandler3 base InnerHandler HashCode: 65273341
DelegatingHandler3 start
/api/values
DelegatingHandler3 end
DelegatingHandler2 end
DelegatingHandler1 end

输出中我们可以看到 DelegatingHandler1 的 InnerHandler 是 DelegatingHandler2,以此类推,在 DelegatingHandler3 的 InnerHandler 处理请求的时候就转发到了相关控制器,这里和 .net core 中的中间件非常相似,在.net core 中间件顺序是 RequestServicesContainerMiddleware(给请求上下文绑定容器)-> AuthenticationMiddleware(认证)-> RouterMiddleware (路由以及MVC)

如果我们在 ValuesController 中观察表达式 this.RequestContext.Configuration.MessageHandlers 还可以看到最终处理请求的是一个 HttpRoutingDispatcher,最也是是分配到路由以及控制器来处理的,按照如此方式我们可以很容易在 asp.net web api 中对请求统计。这里是比较简陋的,对此我们可以记录客户端和服务器更详细的信息,包括 IP 地址,http状态码,是否是认证用户等等,但是这篇主要是以 asp.net core 为主的,所以这里就不详细写下去了。