ASP.NET Core 中间件(Middleware)(一)
本文主要目标:记录Middleware的运行原理流程,并绘制流程图。
目录结构:
1、运行环境
2、Demo实践
3、源码追踪
4、AspnetCore内置middleware
一、运行环境
Visual Studio Community 2019 版本 16.8.5
.Net Sdk Version: 5.0.103
二、Demo实践
讲解或学习一个东西的时候,最方便的方式是先写一个Demo。基于此,我写以一个中间件的记录请求输出的实践Demo来理解Middleware。
实体:
public class RequestResponseLog { public string Id { get; set; } public DateTime CreateTime { get; set; } public string RequestJson { get; set; } public string ResponseJson { get; set; } } public class Student { public string Id { get; set; } public string Name { get; set; } /// <summary> /// 学校 /// </summary> public string School { get; set; } /// <summary> /// 班级 /// </summary> public string Class { get; set; } /// <summary> /// 年级 /// </summary> public string Grade { get; set; } }
Controller:用于接收请求
[Route("api/[controller]")] [ApiController] public class StudentController : Controller { [HttpGet("GetStudent")] public IActionResult GetStudent() { var student = new Student() { Id = Guid.NewGuid().ToString(), Class = "321", Grade = "23", Name = "Name001", School = "School002" }; return Ok(student); } }
Middleware 中间件(记录Request和Response):
public class RequestResponseLoggingMiddleware { private RequestDelegate _next; public RequestResponseLoggingMiddleware(RequestDelegate next) { this._next = next; } /// <summary> /// /// </summary> /// <param name="httpContext"></param> /// <returns></returns> public async Task Invoke(HttpContext context) { //First, get the incoming request var request = await FormatRequest(context.Request); var body = context.Response.Body; //Copy a pointer to the original response body stream var originalBodyStream = context.Response.Body; //Create a new memory stream... using (var responseBody = new MemoryStream()) { //...and use that for the temporary response body context.Response.Body = responseBody; //Continue down the Middleware pipeline, eventually returning to this class await _next(context); //Format the response from the server var response = await FormatResponse(context.Response); //TODO: Save log to chosen datastore,临时使用 DemoQueueBlock<RequestResponseLog>.Add(new RequestResponseLog() { Id=Guid.NewGuid().ToString(), CreateTime = DateTime.Now, ResponseJson = response, RequestJson = request }); //Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client. await responseBody.CopyToAsync(originalBodyStream); } }
为了防止实时存储数据库压力过大,仓储部分用了BlockingCollection实现的简易队列。
blockingcollection-1.getconsumingenumerable
public static void Consume(Action<T> func) { Task.Factory.StartNew(() => { foreach (var item in Colls.GetConsumingEnumerable()) { func(item); Console.WriteLine(string.Format("---------------: {0}", item)); } }); }
消费队列时入库:
public class DemoConsume { private readonly MysqlDbContext _dbContext; public DemoConsume(MysqlDbContext dbContext) { _dbContext = dbContext; } public bool Consume() { DemoQueueBlock<RequestResponseLog>.Consume(async (log)=> { await _dbContext.AddAsync(log); await _dbContext.SaveChangesAsync(); }); return true; } }
StartUp文件AddConsume和
app.UseMiddleware
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); var connection = Configuration.GetConnectionString("MysqlConnection"); services.AddDbContext<MysqlDbContext>(options => options.UseMySQL(connection),ServiceLifetime.Scoped); services.AddConsume(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseMiddleware<RequestResponseLoggingMiddleware>(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Sql语句:
CREATE TABLE `request_response_log` ( `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `create_time` datetime(0) NULL DEFAULT NULL, `request_json` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `response_json` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
运行程序效果:
可以看到该Demo提供了一个记录Http请求和输出日志的功能。
这里面和Middleware有关的功能为:
1、定义了RequestResponseLoggingMiddleware类
RequestDelegate向下转发请求,
Invoke方法
2、StartUp的app.UseMiddleware
这些方法具体怎么流转运行的呢?我们来搜一下源码可以确认下。
三、源码跟踪
所以我们可以看下UseMiddlewareExtensions
public static class UseMiddlewareExtensions { internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync"; /// <summary> /// Adds a middleware type to the application's request pipeline. /// </summary> /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param> /// <param name="middleware">The middleware type.</param> /// <param name="args">The arguments to pass to the middleware type instance's constructor.</param> /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns> public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args) { if (typeof(IMiddleware).IsAssignableFrom(middleware)) { // IMiddleware doesn't support passing args directly since it's // activated from the container if (args.Length > 0) { throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware))); } return UseMiddlewareInterface(app, middleware); } var applicationServices = app.ApplicationServices; return app.Use(next => { var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal) ).ToArray(); if (invokeMethods.Length > 1) { throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName)); } if (invokeMethods.Length == 0) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware)); } var methodInfo = invokeMethods[0]; if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task))); } var parameters = methodInfo.GetParameters(); if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext))); } var ctorArgs = new object[args.Length + 1]; ctorArgs[0] = next; Array.Copy(args, 0, ctorArgs, 1, args.Length); var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); if (parameters.Length == 1) { return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance); } var factory = Compile<object>(methodInfo, parameters); return context => { var serviceProvider = context.RequestServices ?? applicationServices; if (serviceProvider == null) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider))); } return factory(instance, context, serviceProvider); }; }); }
这里面用了
UseMiddleware
UseMiddleware(type TMiddleware)
进行了如下判断:
1、如果TMiddleware是继承了IMiddleware,则执行UseMiddlewareInterface方法。利用IMiddlewareFactory提供中间件的工厂创建方式,Microsoft.AspNetCore.Http提供了IMiddlewareFactory的默认实现MiddlewareFactory。
return app.Use(next => { return async context => { var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory)); if (middlewareFactory == null) { // No middleware factory throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory))); } var middleware = middlewareFactory.Create(middlewareType); if (middleware == null) { // The factory returned null, it's a broken implementation throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType)); } try { await middleware.InvokeAsync(context, next); } finally { middlewareFactory.Release(middleware); } }; });
2、如果没有继承Middleware,则执行以下操作:
1、根据Invoke或InvokeAsync查找方法
2、验证只存在一个方法
3、验证返回类型为Task
4、验证第一个参数必须是HttpContext
5、ActivatorUtilities.CreateInstance 创建实例
6、如果只有一个参数,返回一个RequestDelegate类型的方法委托?
7、多个参数继续执行如下操作。Compile方法和参数。
var factory = Compile
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?