请选择 进入手机版 | 继续访问电脑版

[ASP.NET] 明白 ASP.NET Core 中央 件(Middleware)

[复制链接]
查看236 | 回复46 | 2021-9-15 07:18:56 | 显示全部楼层 |阅读模式
目次

中心 件

先借用微软官方文档的一张图:

明白
ASP.NET Core 中央

件(Middleware)

可以看到,中心 件现实 上是一种设置 在HTTP哀求 管道中,用来处理哀求 和相应 的组件。它可以:

  • 决定是否将哀求 传递到管道中的下一个中心 件
  • 可以在管道中的下一个中心 件处理之前和之后举行 操作

此外,中心 件的注册是有次序 的,誊写 代码时肯定 要留意 !

中心 件管道

Run

该方法为HTTP哀求 管道添加一个中心 件,并标识该中心 件为管道止境 ,称为终端中心 件。也就是说,该中心 件就是管道的末端 ,在该中心 件之后注册的中心 件将永久 都不会被实验 。以是 ,该方法一样平常 只会誊写 在

  1. Configure
复制代码
方法末端 。

  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. app.Run(async context =>
  6. {
  7. await context.Response.WriteAsync("Hello, World!");
  8. });
  9. }
  10. }
复制代码

Use

通过该方法快捷的注册一个匿名的中心 件

  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. app.Use(async (context, next) =>
  6. {
  7. // 下一个中间件处理之前的操作
  8. Console.WriteLine("Use Begin");
  9. await next();
  10. // 下一个中间件处理完成后的操作
  11. Console.WriteLine("Use End");
  12. });
  13. }
  14. }
复制代码

留意 :

  • 1.假如 要将哀求 发送到管道中的下一个中心 件,肯定 要记得调用
    1. next.Invoke / next()
    复制代码
    ,否则会导致管道短路,后续的中心 件将不会被实验
  • 2.在中心 件中,假如 已经开始给客户端发送
    1. Response
    复制代码
    ,请万万 不要调用
    1. next.Invoke / next()
    复制代码
    ,也不要对
    1. Response
    复制代码
    举行 任何更改,否则,将抛出非常 。
  • 3.可以通过
    1. context.Response.HasStarted
    复制代码
    来判断 相应 是否已开始。

以下都是错误的代码写法

错误1:

  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. app.Use(async (context, next) =>
  6. {
  7. await context.Response.WriteAsync("Use");
  8. await next();
  9. });
  10. app.Run(context =>
  11. {
  12. // 由于上方的中间件已经开始 Response,此处更改 Response Header 会抛出异常
  13. context.Response.Headers.Add("test", "test");
  14. return Task.CompletedTask;
  15. });
  16. }
  17. }
复制代码

错误2

  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. app.Use(async (context, next) =>
  6. {
  7. await context.Response.WriteAsync("Use");
  8. // 即使没有调用 next.Invoke / next(),也不能在 Response 开始后对 Response 进行更改
  9. context.Response.Headers.Add("test", "test");
  10. });
  11. }
  12. }
复制代码

UseWhen

通过该方法针对不同的逻辑条件创建管道分支。必要 留意 的是:

进入了管道分支后,假如 管道分支不存在管道短路或终端中心 件,则会再次返回到主管道。

当使用

  1. PathString
复制代码
时,路径必须以“/”开头,且答应 只有一个
  1. '/'
复制代码
字符

支持嵌套,即UseWhen中嵌套UseWhen等

支持同时匹配多个段,如 /get/user

  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. // /get 或 /get/xxx 都会进入该管道分支
  6. app.UseWhen(context => context.Request.Path.StartsWithSegments("/get"), app =>
  7. {
  8. app.Use(async (context, next) =>
  9. {
  10. Console.WriteLine("UseWhen:Use");
  11. await next();
  12. });
  13. });
  14. app.Use(async (context, next) =>
  15. {
  16. Console.WriteLine("Use");
  17. await next();
  18. });
  19. app.Run(async context =>
  20. {
  21. Console.WriteLine("Run");
  22. await context.Response.WriteAsync("Hello World!");
  23. });
  24. }
  25. }
复制代码

当访问 /get 时,输出如下:

  1. UseWhen:Use
  2. Use
  3. Run
复制代码
  1. 如果你发现输出了两遍,别慌,看看是不是浏览器发送了两次请求,分别是 /get 和 /favicon.ico
复制代码

Map

  • 通过该方法针对不同的哀求 路径创建管道分支。必要 留意 的是:
  • 一旦进入了管道分支,则不会再回到主管道。
  • 使用 该方法时,会将匹配的路径从
    1. HttpRequest.Path
    复制代码
    中删除,并将其追加到
    1. HttpRequest.PathBase
    复制代码
    中。
  • 路径必须以“/”开头,且不能只有一个
    1. '/'
    复制代码
    字符
  • 支持嵌套,即Map中嵌套Map、MapWhen(接下来会讲)等
  • 支持同时匹配多个段,如 /post/user
  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. // 访问 /get 时会进入该管道分支
  6. // 访问 /get/xxx 时会进入该管道分支
  7. app.Map("/get", app =>
  8. {
  9. app.Use(async (context, next) =>
  10. {
  11. Console.WriteLine("Map get: Use");
  12. Console.WriteLine($"Request Path: {context.Request.Path}");
  13. Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
  14. await next();
  15. });
  16. app.Run(async context =>
  17. {
  18. Console.WriteLine("Map get: Run");
  19. await context.Response.WriteAsync("Hello World!");
  20. });
  21. });
  22. // 访问 /post/user 时会进入该管道分支
  23. // 访问 /post/user/xxx 时会进入该管道分支
  24. app.Map("/post/user", app =>
  25. {
  26. // 访问 /post/user/student 时会进入该管道分支
  27. // 访问 /post/user/student/1 时会进入该管道分支
  28. app.Map("/student", app =>
  29. {
  30. app.Run(async context =>
  31. {
  32. Console.WriteLine("Map /post/user/student: Run");
  33. Console.WriteLine($"Request Path: {context.Request.Path}");
  34. Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
  35. await context.Response.WriteAsync("Hello World!");
  36. });
  37. });
  38. app.Use(async (context, next) =>
  39. {
  40. Console.WriteLine("Map post/user: Use");
  41. Console.WriteLine($"Request Path: {context.Request.Path}");
  42. Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
  43. await next();
  44. });
  45. app.Run(async context =>
  46. {
  47. Console.WriteLine("Map post/user: Run");
  48. await context.Response.WriteAsync("Hello World!");
  49. });
  50. });
  51. }
  52. }
复制代码

当你访问 /get/user 时,输出如下:

  1. Map get: Use
  2. Request Path: /user
  3. Request PathBase: /get
  4. Map get: Run
复制代码

当你访问 /post/user/student/1 时,输出如下:

  1. Map /post/user/student: Run
  2. Request Path: /1
  3. Request PathBase: /post/user/student
复制代码

其他环境 交给你本身 去尝试啦!

MapWhen

  1. Map
复制代码
类似 ,只不过
  1. MapWhen
复制代码
不是基于路径,而是基于逻辑条件创建管道分支。留意 事项如下:

  • 一旦进入了管道分支,则不会再回到主管道。
  • 当使用
    1. PathString
    复制代码
    时,路径必须以“/”开头,且答应 只有一个
    1. '/'
    复制代码
    字符
    1. HttpRequest.Path
    复制代码
    1. HttpRequest.PathBase
    复制代码
    不会像
    1. Map
    复制代码
    那样举行 特别 处理
  • 支持嵌套,即MapWhen中嵌套MapWhen、Map等
  • 支持同时匹配多个段,如 /get/user
  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. // /get 或 /get/xxx 都会进入该管道分支
  6. app.MapWhen(context => context.Request.Path.StartsWithSegments("/get"), app =>
  7. {
  8. app.MapWhen(context => context.Request.Path.ToString().Contains("user"), app =>
  9. {
  10. app.Use(async (context, next) =>
  11. {
  12. Console.WriteLine("MapWhen get user: Use");
  13. await next();
  14. });
  15. });
  16. app.Use(async (context, next) =>
  17. {
  18. Console.WriteLine("MapWhen get: Use");
  19. await next();
  20. });
  21. app.Run(async context =>
  22. {
  23. Console.WriteLine("MapWhen get: Run");
  24. await context.Response.WriteAsync("Hello World!");
  25. });
  26. });
  27. }
  28. }
复制代码

当你访问 /get/user 时,输出如下:

  1. MapWhen get user: Use
复制代码

可以看到,即使该管道分支没有终端中心 件,也不会回到主管道。

Run & Use & UseWhen & Map & Map

一下子打仗 了4个定名 相似的、与中心 件管道有关的API,不知道你有没有晕倒,不要紧 ,我来帮大家总结一下:

    1. Run
    复制代码
    用于注册终端中心 件,
    1. Use
    复制代码
    用来注册匿名中心 件,
    1. UseWhen
    复制代码
    1. Map
    复制代码
    1. MapWhen
    复制代码
    用于创建管道分支。
    1. UseWhen
    复制代码
    进入管道分支后,假如 管道分支中不存在短路或终端中心 件,则会返回到主管道。
    1. Map
    复制代码
    1. MapWhen
    复制代码
    进入管道分支后,无论怎样 ,都不会再返回到主管道。
    1. UseWhen
    复制代码
    1. MapWhen
    复制代码
    基于逻辑条件来创建管道分支,而
    1. Map
    复制代码
    基于哀求 路径来创建管道分支,且会对
    1. HttpRequest.Path
    复制代码
    1. HttpRequest.PathBase
    复制代码
    举行 处理。

编写中心 件并激活

上面已经提到过的

  1. Run
复制代码
  1. Use
复制代码
就不再赘述了。

基于约定的中心 件

“约定大于设置 ”,先来个约法三章:

  • 1.拥有公共(public)构造函数,且该构造函数至少包含一个范例 为
    1. RequestDelegate
    复制代码
    的参数
  • 2.拥闻名 为
    1. Invoke
    复制代码
    1. InvokeAsync
    复制代码
    的公共(public)方法,必须包含一个范例 为
    1. HttpContext
    复制代码
    的方法参数,且该参数必须位于第一个参数的位置,别的 该方法必须返回
    1. Task
    复制代码
    范例 。
  • 3.构造函数中的其他参数可以通过依赖 注入(DI)添补 ,也可以通过
    1. UseMiddleware
    复制代码
    传参举行 添补 。

通过DI添补 时,只能吸收 Transient 和 Singleton 的DI参数。这是由于中心 件是在应用启动时构造的(而不是按哀求 构造),以是 当出现 Scoped 参数时,构造函数内的DI参数生命周期与其他不共享,假如 想要共享,则必须将Scoped DI参数添加到

  1. Invoke/InvokeAsync
复制代码
来举行 使用 。

通过

  1. UseMiddleware
复制代码
传参时,构造函数内的DI参数和非DI参数次序 没有要求,传入
  1. UseMiddleware
复制代码
内的参数次序 也没有要求,但是我建议将非DI参数放到前面,DI参数放到后面。(这一块感觉微软做的好牛皮)

  • 4.
    1. Invoke/InvokeAsync
    复制代码
    的其他参数也可以或许 通过依赖 注入(DI)添补 ,可以吸收 Transient、Scoped 和 Singleton 的DI参数。

一个简单的中心 件如下:

  1. public class MyMiddleware
  2. {
  3. // 用于调用管道中的下一个中间件
  4. private readonly RequestDelegate _next;
  5. public MyMiddleware(
  6. RequestDelegate next,
  7. ITransientService transientService,
  8. ISingletonService singletonService)
  9. {
  10. _next = next;
  11. }
  12. public async Task InvokeAsync(
  13. HttpContext context,
  14. ITransientService transientService,
  15. IScopedService scopedService,
  16. ISingletonService singletonService)
  17. {
  18. // 下一个中间件处理之前的操作
  19. Console.WriteLine("MyMiddleware Begin");
  20. await _next(context);
  21. // 下一个中间件处理完成后的操作
  22. Console.WriteLine("MyMiddleware End");
  23. }
  24. }
复制代码

然后,你可以通过

  1. UseMiddleware
复制代码
方法将其添加到管道中

  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. app.UseMiddleware<MyMiddleware>();
  6. }
  7. }
复制代码

不过,一样平常 不保举 直接使用

  1. UseMiddleware
复制代码
,而是将其封装到扩展方法中

  1. public static class AppMiddlewareApplicationBuilderExtensions
  2. {
  3. public static IApplicationBuilder UseMy(this IApplicationBuilder app) => app.UseMiddleware<MyMiddleware>();
  4. }
  5. public class Startup
  6. {
  7. public void Configure(IApplicationBuilder app)
  8. {
  9. app.UseMy();
  10. }
  11. }
复制代码

基于工厂的中心 件

上风 :

  • 按照哀求 举行 激活。这个就是说,上面基于约定的中心 件实例是单例的,但是基于工厂的中心 件,可以在依赖 注入时设置中心 件实例的生命周期。
  • 使中心 件强范例 化(由于 着实 现了接口
    1. IMiddleware
    复制代码

该方式的实现基于

  1. IMiddlewareFactory
复制代码
  1. IMiddleware
复制代码
。先来看一下接口定义:

  1. public interface IMiddlewareFactory
  2. {
  3. IMiddleware? Create(Type middlewareType);
  4. void Release(IMiddleware middleware);
  5. }
  6. public interface IMiddleware
  7. {
  8. Task InvokeAsync(HttpContext context, RequestDelegate next);
  9. }
复制代码

你有没有想过当我们调用

  1. UseMiddleware
复制代码
时,它是怎样 工作的呢?毕竟 上,
  1. UseMiddleware
复制代码
扩展方法会先检查中心 件是否实现了
  1. IMiddleware
复制代码
接口。 假如 实现了,则使用 容器中注册的
  1. IMiddlewareFactory
复制代码
实例来分析 该
  1. IMiddleware
复制代码
的实例(这下你知道为什么称为“基于工厂的中心 件”了吧)。假如 没实现,那么就使用 基于约定的中心 件逻辑来激活中心 件。

留意 ,基于工厂的中心 件,在应用的服务容器中一样平常 注册为 Scoped 或 Transient 服务

如许 的话,咱们就可以放心的将 Scoped 服务注入到中心 件的构造函数中了。

接下来,咱们就来实现一个基于工厂的中心 件:

  1. public class YourMiddleware : IMiddleware
  2. {
  3. public async Task InvokeAsync(HttpContext context, RequestDelegate next)
  4. {
  5. // 下一个中间件处理之前的操作
  6. Console.WriteLine("YourMiddleware Begin");
  7. await next(context);
  8. // 下一个中间件处理完成后的操作
  9. Console.WriteLine("YourMiddleware End");
  10. }
  11. }
  12. public static class AppMiddlewareApplicationBuilderExtensions
  13. {
  14. public static IApplicationBuilder UseYour(this IApplicationBuilder app) => app.UseMiddleware<YourMiddleware>();
  15. }
复制代码

然后,在

  1. ConfigureServices
复制代码
中添加中心 件依赖 注入

  1. public class Startup
  2. {
  3. public void ConfigureServices(IServiceCollection services)
  4. {
  5. services.AddTransient<YourMiddleware>();
  6. }
  7. }
复制代码

末了 ,在

  1. Configure
复制代码
中使用 中心 件

  1. public class Startup
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. app.UseYour();
  6. }
  7. }
复制代码

微软提供了

  1. IMiddlewareFactory
复制代码
的默认实现:

  1. public class MiddlewareFactory : IMiddlewareFactory
  2. {
  3. // The default middleware factory is just an IServiceProvider proxy.
  4. // This should be registered as a scoped service so that the middleware instances
  5. // don't end up being singletons.
  6. // 默认的中间件工厂仅仅是一个 IServiceProvider 的代理
  7. // 该工厂应该注册为 Scoped 服务,这样中间件实例就不会成为单例
  8. private readonly IServiceProvider _serviceProvider;
  9. public MiddlewareFactory(IServiceProvider serviceProvider)
  10. {
  11. _serviceProvider = serviceProvider;
  12. }
  13. public IMiddleware? Create(Type middlewareType)
  14. {
  15. return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware;
  16. }
  17. public void Release(IMiddleware middleware)
  18. {
  19. // The container owns the lifetime of the service
  20. // DI容器来管理服务的生命周期
  21. }
  22. }
复制代码

可以看到,该工厂使用 过DI容器来分析 出服务实例的。因此,当使用 基于工厂的中心 件时,是无法通过

  1. UseMiddleware
复制代码
向中心 件的构造函数传参的。

基于约定的中心 件 VS 基于工厂的中心 件

  • 基于约定的中心 件实例都是 Singleton;而基于工厂的中心 件实例可以是 Singleton、Scoped 和 Transient(当然,不建议注册为 Singleton)
  • 基于约定的中心 件实例构造函数中可以通过依赖 注入传参,也可以用过
    1. UseMiddleware
    复制代码
    传参;而基于工厂的中心 件只能通过依赖 注入传参
  • 基于约定的中心 件实例可以在
    1. Invoke/InvokeAsync
    复制代码
    中添加更多的依赖 注入参数;而基于工厂的中心 件只能按照
    1. IMiddleware
    复制代码
    的接口定义举行 实现。

到此这篇关于明白 ASP.NET Core 中心 件(Middleware)的文章就先容 到这了,更多相干 ASP.NET Core Middleware内容请搜索 脚本之家从前 的文章或继续欣赏 下面的相干 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 银鲜目江探 | 2021-9-15 10:32:56 | 显示全部楼层
我对admin楼主的敬仰犹如滔滔江水绵延不绝!
回复

使用道具 举报

avatar fsxjjv | 2021-9-22 01:00:19 | 显示全部楼层
有机会找admin楼主好好聊聊!
回复

使用道具 举报

avatar 123457776 | 2021-9-22 17:37:52 | 显示全部楼层
收藏了,很不错的内容!
回复

使用道具 举报

avatar Rudy64077 | 2021-9-23 09:49:54 | 显示全部楼层
论坛的帖子越来越有深度了!
回复

使用道具 举报

avatar 此路难行谎 | 2021-9-27 23:55:41 | 显示全部楼层
我只是来赚积分的!
回复

使用道具 举报

avatar 快乐人L | 2021-9-29 00:58:38 | 显示全部楼层
admin楼主是一个典型的文艺青年啊!
回复

使用道具 举报

avatar 封号955 | 2021-9-29 19:41:06 | 显示全部楼层
这个帖子好无聊啊!
回复

使用道具 举报

avatar 都市夜归人PLA | 2021-10-5 01:44:16 | 显示全部楼层
雷锋做好事不留名,都写在帖子里!
回复

使用道具 举报

avatar 边锋1 | 2021-10-5 02:25:34 | 显示全部楼层
很给力!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则