目录1.前言2.使用中间件2.1 Run2.2 Use2.3 Map和MapWhen3.顺序4.编写中间件(重点)4.1中间件类4.2中间件扩展方法5.按每次请求创建依赖注入(DI)
整个Http Request请求跟HTTP Response返回结果之间的处理流程是一个请求管道(request pipeline)。而中间件(middleware)则是一种装配到请求管道以处理请求和响应的组件。每个组件:
中间件(middleware)处理流程如下图所示:
ASP.net core请求管道中每个中间件都包含一系列的请求委托(request delegates)来处理每个HTTP请求,依次调用。请求委托通过使用IApplicationBuilder类型的Run、Use和Map扩展方法在Strartup.Configure方法中配置。下面我们通过配置Run、Use和Map扩展方法示例来了解下中间件。
public class Startup
{
public void Configure(IApplicationBuilder app)
{
//第一个请求委托Run
app.Run(async context =>//内嵌匿名方法
{
await context.Response.WriteAsync("Hello, World!");
});
//第二个请求委托Run
app.Run(async context =>//内嵌匿名方法
{
await context.Response.WriteAsync("Hey, World!");
});
}
}
响应结果:
由上述代码可知,Run方法指定为一个内嵌匿名方法(称为并行中间件,in-line middleware),而内嵌匿名方法中并没有指定执行下一个请求委托,这一个过程叫管道短路,而该中间件又叫“终端中间件”(terminal middleware),因为它阻止中间件下一步处理请求。所以在Run第一个请求委托的时候就已经终止请求,并没有执行第二个请求委托直接返回Hello, World!输出文本。而根据官网解释,Run是一种约定,有些中间件组件可能会暴露他们自己的Run方法,而这些方法只能在管道末尾处运行(也就是说Run方法只在中间件执行最后一个请求委托时才使用)。
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync("进入第一个委托 执行下一个委托之前\r\n");
//调用管道中的下一个委托
await next.Invoke();
await context.Response.WriteAsync("结束第一个委托 执行下一个委托之后\r\n");
});
app.Run(async context =>
{
await context.Response.WriteAsync("进入第二个委托\r\n");
await context.Response.WriteAsync("Hello from 2nd delegate.\r\n");
await context.Response.WriteAsync("结束第二个委托\r\n");
});
}
响应结果:
由上述代码可知,Use方法将多个请求委托链接在一起。而next参数表示管道中的下一个委托。如果不调用next参数调用下一个请求委托则会使管道短路。比如,一个授权(authorization)中间件只有通过身份验证之后才能调用下一个委托,否则它就会被短路,并返回“Not Authorized”的响应。所以应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。
Map示例:
public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}
下面表格使用前面的代码显示来自http://localhost:5001的请求和响应。
请求 | 响应 |
localhost:5001 | Hello from non-Map delegate. |
localhost:5001/map1 | Map Test 1 |
localhost:5001/map2 | Map Test 2 |
localhost:5001/map3 | Hello from non-Map delegate. |
由上述代码可知,Map方法将从HttpRequest.Path中删除匹配的路径段,并针对每个请求将该路径追加到HttpRequest.PatHBase。也就是说当我们在浏览器上输入map1请求地址的时候,系统会执行map1分支管道输出其请求委托信息,同理执行map2就会输出对应请求委托信息。
MapWhen示例:
public class Startup
{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}
public void Configure(IApplicationBuilder app)
{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}
下面表格使用前面的代码显示来自http://localhost:5001的请求和响应。
请求 | 响应 |
http://localhost:5001 | Hello from non-Map delegate. <p> |
https://localhost:5001/?branch=master | Branch used = master |
由上述代码可知,MapWhen是基于branch条件而创建管道分支的,我们在branch条件上输入master就会创建其对应管道分支。也就是说,branch条件上输入任何一个字符串条件,都会创建一个新的管理分支。
而且还Map支持嵌套,例如:
public void Configure(IApplicationBuilder app)
{
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
}
还可同时匹配多个段:
public class Startup
{
private static void HandleMultiSeg(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map multiple segments.");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/map1/seg1", HandleMultiSeg);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
}
}
向Startup.Configure方法添加中间件组件的顺序定义了在请求上调用它们的顺序,以及响应的相反顺序。此排序对于安全性、性能和功能至关重要。
以下Startup.Configure方法将为常见应用方案添加中间件组件:
请看如下代码:
public void Configure(IApplicationBuilder app)
{
if (env.IsDevelopment())
{
// When the app runs in the Development environment:
// Use the Developer Exception Page to report app runtime errors.
// Use the Database Error Page to report database runtime errors.
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
// When the app doesn't run in the Development environment:
// Enable the Exception Handler Middleware to catch exceptions
// thrown in the following middlewares.
// Use the HTTP Strict Transport Security Protocol (HSTS)
// Middleware.
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Return static files and end the pipeline.
app.UseStaticFiles();
// Authenticate before the user accesses secure resources.
app.UseAuthentication();
}
从上述示例代码中,每个中间件扩展方法都通过Microsoft.Aspnetcore.Builder命名空间在 IApplicationBuilder上公开。但是为什么我们要按照这个顺序去添加中间件组件呢?下面我们挑几个中间件来了解下。
经过上面描述,大家都了解中间件顺序的重要性了吧。
虽然ASP.net core为我们提供了一组丰富的内置中间件组件,但在某些情况下,你可能需要写入自定义中间件。
下面我们自定义一个查询当前区域性的中间件:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
return next();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}
可通过传入区域性参数条件进行测试。例如http://localhost:7997/?culture=zh、http://localhost:7997/?culture=en。
但是如果每个自定义中间件都在Startup.Configure方法中编写如上一大堆代码,那么对于程序来说,将是灾难性的(不利于维护和调用)。为了更好管理代码,我们应该把内嵌匿名方法封装到新建的自定义类(示例自定义RequestCultureMiddleware类)里面去:
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.ContentType = "text/plain; charset=utf-8";
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
通过Startup.Configure方法调用中间件:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<RequestCultureMiddleware>();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}
Startup.Configure方法调用中间件设置可以通过自定义的扩展方法将其公开(调用内置IApplicationBuilder公开中间件)。示例创建一个RequestCultureMiddlewareExtensions扩展类并通过IApplicationBuilder公开:
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
再通过Startup.Configure方法调用中间件:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}
响应结果:
通过委托构造中间件,应用程序在运行时创建这个中间件,并将它添加到管道中。这里需要注意的是,中间件的创建是单例的,每个中间件在应用程序生命周期内只有一个实例。那么问题来了,如果我们业务逻辑需要多个实例时,该如何操作呢?请继续往下看。
在中间件的创建过程中,内置的ioc容器会为我们创建一个中间件实例,并且整个应用程序生命周期中只会创建一个该中间件的实例。通常我们的程序不允许这样的注入逻辑。其实,我们可以把中间件理解成业务逻辑的入口,真正的业务逻辑是通过Application Service层实现的,我们只需要把应用服务注入到Invoke方法中即可。ASP.Net Core为我们提供了这种机制,允许我们按照请求进行依赖的注入,也就是每次请求创建一个服务。示例:
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
// IMyScopedService is injected into Invoke
public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
{
svc.MyProperty(1000);
await _next(httpContext);
}
}
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}
public interface IMyScopedService
{
void MyProperty(decimal input);
}
public class MyScopedService : IMyScopedService
{
public void MyProperty(decimal input)
{
Console.WriteLine("MyProperty is " + input);
}
}
public void ConfigureServices(IServiceCollection services)
{
//注入DI服务
services.AddScoped<IMyScopedService, MyScopedService>();
}
响应结果:
--结束END--
本文标题: ASP.NETCore中间件
本文链接: https://lsjlt.com/news/145114.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2023-05-21
2023-05-21
2023-05-21
2023-05-21
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0