public class ApiInsightMiddleware { private readonly RequestDelegate _next; private readonly IServiceProvider _serverProvider; private readonly IApiInsightsKeys _apiInsightsKeys; private readonly ILogger<ApiInsightMiddleware> _logger; private HttpContext _httpContext; public ApiInsightMiddleware(RequestDelegate next, IServiceProvider serviceProvider, ILogger<ApiInsightMiddleware> logger) { _next = next; _serverProvider = serviceProvider; _apiInsightsKeys = _serverProvider.GetService<IApiInsightsKeys>(); _logger = logger; } public async Task Invoke(HttpContext httpContext) { _httpContext = httpContext; var flag = SetValues(); await _next(httpContext); if (flag == true) { ApiInsight(); } } //省略了其他的代码 }
很好理解,在执行下一个中间件之前调用 SetValues 开始计时,下一个中间件执行成功开始统计并写入日志(或者忽略不写)。现在他是 asp.net core mvc 的第一个中间件了,好处就是更符合这个中间件本身的所做的事情了,但是带来的问题就是 httpContext.RequestService 是 null ,因为 RequestService 是在 RequestServicesContainerMiddleware 这个中间件写进去的,在者其实很多地方我们都需要 HttpContext ,并且目前微软还没有给我们定义一个静态的 HttpContext。
静态的 HttpContext
HttpContext 是通过单例 IHttpContextAccessor 提供的,当 HttpContext 创建的时候就会赋值给他,当请求到达中间件这个管道的时候,HttpContext 就已经存在于 IHttpContextAccessor 了,并且和 Invoke 参数列表中的 HttpContext 是一致的(同一个请求中),问题在于 RequestServicesContainerMiddleware 这个中间件没有执行就没有容器,并且很多时候我们都要用到容器,所以就模仿源码在这里都加进去了。
public static class HttpContextProvider { private static IHttpContextAccessor _accessor; private static IServiceScopeFactory _serviceScopeFactory; public static Microsoft.AspNetCore.Http.HttpContext Current { get { var context = _accessor?.HttpContext; if (context != null) { var replacementFeature = new RequestServicesFeature(_serviceScopeFactory); context.Features.Set<IServiceProvidersFeature>(replacementFeature); return context; } return null; } } internal static void ConfigureAccessor(IHttpContextAccessor accessor, IServiceScopeFactory serviceScopeFactory) { _accessor = accessor; _serviceScopeFactory = serviceScopeFactory; } } public static class HttpContextExtenstion { public static void AddHttpContextAccessor(this IServiceCollection services) { services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); } public static IApplicationBuilder UseGlobalHttpContext(this IApplicationBuilder app) { var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>(); var serviceScopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); HttpContextProvider.ConfigureAccessor(httpContextAccessor, serviceScopeFactory); return app; } }
我们只需要在 Startup 中使用 app.UseGlobalHttpContext(); 就可以在程序的任何地方得到 HttpContext 和容器了,肯定会有人说为什么不通过构造函数来获取我们想要的注入呢,因为有些第三方框架或这某些地方我们不能使用容器获取服务,比如这里 NLog 的自定义字段使用的 LayoutRenderer 就无法通过构造器得到我们想要的服务。
第一个中间件
在 Startup 的 Configure 方法中目前还没发现如何注册第一个中间件,因为 Configure 方法始终是在 IStartupFilter 这个接口之后执行的,这也提供了我们让自己的中间件成为第一个中间件的可能。可能这样做并不是特别有必要,甚至是没有意义的,但是实现的过程确实很有意思的。这里在 Startup 中的 方法 ConfigureService 注册我们的中间件。
public void ConfigureServices(IServiceCollection services) { services.AddApiInsights(); services.AddMvc(); }
具体的
public static class ApiInsightsServiceCollectionExtensions { static readonly string stopWatchName = "__stopwatch__"; static readonly string startTimeName = "__start__"; /// <summary> /// 注册和 API 监控相关的服务,中间件 /// </summary> /// <param></param> public static void AddApiInsights(this IServiceCollection services) { services.AddSingleton<IApiInsightsKeys>( new ApiInsightsKeys(stopWatchName, startTimeName) ); services.FirstRegister<IStartupFilter, RequestApiInsightBeginStartupFilter>(ServiceCollectionServiceExtensions.AddTransient<IStartupFilter, RequestApiInsightBeginStartupFilter>); services.AddSingleton<IRequestIsAuthenticate, DefaultRequestIsAuthenticate>(); } }
这里注册了三个服务
IApiInsightsKeys
定义了存储在 HttpContext.Item 中的键值对的名称
public interface IApiInsightsKeys { string StopWatchName { get; } string StartTimeName { get; } }
IRequestIsAuthenticate