ASP.NET Core 中介軟體(Middleware)(一)
阿新 • • 發佈:2021-02-14
本文主要目標:記錄Middleware的執行原理流程,並繪製流程圖。
目錄結構:
>1、執行環境
>2、Demo實踐
>3、原始碼追蹤
>4、AspnetCore內建middleware
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210214190655198-262907590.png)
#一、執行環境
Visual Studio Community 2019 版本 16.8.5
.Net Sdk Version: 5.0.103
#二、Demo實踐
講解或學習一個東西的時候,最方便的方式是先寫一個Demo。基於此,我寫以一箇中間件的記錄請求輸出的實踐Demo來理解Middleware。
實體:
``` c#
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; }
///
/// 學校
///
public string School { get; set; }
///
/// 班級
///
public string Class { get; set; }
///
/// 年級
///
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;
}
///
///
///
///
///
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.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
>https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.blockingcollection-1.getconsumingenumerable?view=net-5.0
```
public static void Consume(Action 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.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(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();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
```
Sql語句:
``` 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;
```
執行程式效果:
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210214191110194-2011025163.png)
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210214191223070-1495071610.png)
可以看到該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";
///
/// Adds a middleware type to the application's request pipeline.
///
/// The instance.
/// The middleware type.
/// The arguments to pass to the middleware type instance's constructor.
/// The instance.
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