【.NET Core框架】中间件

简介

中间件实际上是一种配置在HTTP请求管道中,用来处理请求和响应的组件。它可以:

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

注册中间件

Run & Use & UseWhen & Map & MapWhen & UseMiddleWare

注册中间件有这四种方式,它们之间的区别如下:

  • Run用于注册终端中间件,Use用来注册匿名中间件,UseWhen、Map、MapWhen用于创建管道分支。
  • UseWhen进入管道分支后,如果管道分支中不存在短路或终端中间件,则会返回到主管道。Map和MapWhen进入管道分支后,无论如何,都不会再返回到主管道。
  • UseWhen和MapWhen基于逻辑条件来创建管道分支,而Map基于请求路径来创建管道分支,且会对HttpRequest.Path和HttpRequest.PathBase进行处理。

Use()

  • 如果要将请求发送到管道中的下一个中间件,一定要记得调用next.Invoke / next(),否则会导致管道短路,后续的中间件将不会被执行
  • 在中间件中,如果已经开始给客户端发送Response,请千万不要调用next.Invoke / next(),也不要对Response进行任何更改,否则,将抛出异常。
  • 可以通过context.Response.HasStarted来判断响应是否已开始。
//Use重载1
app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("hello world");
                await next.Invoke();
            });
//Use重载2
app.Use((next) =>
            {
                return async (context) =>
                {
                    await context.Response.WriteAsync("hello world2");
                    await next(context);
                };

            });

错误写法
错误1:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("Use");
            await next();
        });

        app.Run(context =>
        {
            // 由于上方的中间件已经开始 Response,此处更改 Response Header 会抛出异常
            context.Response.Headers.Add("test", "test");
            return Task.CompletedTask;
        });
    }
}

错误2:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("Use");
            
            // 即使没有调用 next.Invoke / next(),也不能在 Response 开始后对 Response 进行更改
            context.Response.Headers.Add("test", "test");
        });
    }
}

UseWhen

  • 进入了管道分支后,如果管道分支不存在管道短路或终端中间件,则会再次返回到主管道。
  • 当使用PathString时,路径必须以“/”开头,且允许只有一个'/'字符
  • 支持嵌套,即UseWhen中嵌套UseWhen等
  • 支持同时匹配多个段,如 /get/user
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // /get 或 /get/xxx 都会进入该管道分支
        app.UseWhen(context => context.Request.Path.StartsWithSegments("/get"), app =>
        {
            app.Use(async (context, next) =>
            {
                Console.WriteLine("UseWhen:Use");

                await next();
            });
        });
        
        app.Use(async (context, next) =>
        {
            Console.WriteLine("Use");

            await next();
        });

        app.Run(async context =>
        {
            Console.WriteLine("Run");

            await context.Response.WriteAsync("Hello World!");
        });
    }
}

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

gauss
UseWhen:Use
Use
Run

Map

通过该方法针对不同的请求路径创建管道分支。需要注意的是:

  • 一旦进入了管道分支,则不会再回到主管道。
  • 使用该方法时,会将匹配的路径从HttpRequest.Path 中删除,并将其追加到HttpRequest.PathBase中。
  • 路径必须以“/”开头,且不能只有一个'/'字符
  • 支持嵌套,即Map中嵌套Map、MapWhen(接下来会讲)等
  • 支持同时匹配多个段,如 /post/user
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // 访问 /get 时会进入该管道分支
        // 访问 /get/xxx 时会进入该管道分支
        app.Map("/get", app =>
        {
            app.Use(async (context, next) =>
            {
                Console.WriteLine("Map get: Use");
                Console.WriteLine($"Request Path: {context.Request.Path}"); 
                Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
        
                await next();
            });
        
            app.Run(async context =>
            {
                Console.WriteLine("Map get: Run");
        
                await context.Response.WriteAsync("Hello World!");
            });
        
        });
        
        // 访问 /post/user 时会进入该管道分支
        // 访问 /post/user/xxx 时会进入该管道分支
        app.Map("/post/user", app =>
        {
            // 访问 /post/user/student 时会进入该管道分支
            // 访问 /post/user/student/1 时会进入该管道分支
            app.Map("/student", app =>
            {
                app.Run(async context =>
                {
                    Console.WriteLine("Map /post/user/student: Run");
                    Console.WriteLine($"Request Path: {context.Request.Path}");
                    Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
        
                    await context.Response.WriteAsync("Hello World!");
                });
            });
            
            app.Use(async (context, next) =>
            {
                Console.WriteLine("Map post/user: Use");
                Console.WriteLine($"Request Path: {context.Request.Path}");
                Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
                
                await next();
            });
        
            app.Run(async context =>
            {
                Console.WriteLine("Map post/user: Run");
        
                await context.Response.WriteAsync("Hello World!");
            });
        });
    }
}

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

Map get: Use
Request Path: /user
Request PathBase: /get
Map get: Run

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

Map /post/user/student: Run
Request Path: /1
Request PathBase: /post/user/student

MapWhen

与Map类似,只不过MapWhen不是基于路径,而是基于逻辑条件创建管道分支。注意事项如下:

  • 一旦进入了管道分支,则不会再回到主管道。
  • 当使用PathString时,路径必须以“/”开头,且允许只有一个'/'字符
  • HttpRequest.Path和HttpRequest.PathBase不会像Map那样进行特别处理
  • 支持嵌套,即MapWhen中嵌套MapWhen、Map等
  • 支持同时匹配多个段,如 /get/user
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // /get 或 /get/xxx 都会进入该管道分支
        app.MapWhen(context => context.Request.Path.StartsWithSegments("/get"), app =>
        {
            app.MapWhen(context => context.Request.Path.ToString().Contains("user"), app =>
            {
                app.Use(async (context, next) =>
                {
                    Console.WriteLine("MapWhen get user: Use");

                    await next();
                });
            });
        
            app.Use(async (context, next) =>
            {
                Console.WriteLine("MapWhen get: Use");
        
                await next();
            });
        
            app.Run(async context =>
            {
                Console.WriteLine("MapWhen get: Run");
        
                await context.Response.WriteAsync("Hello World!");
            });
        });
    }
}

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

MapWhen get user: Use

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

Run

该方法为HTTP请求管道添加一个中间件,并标识该中间件为管道终点,称为终端中间件。也就是说,该中间件就是管道的末尾,在该中间件之后注册的中间件将永远都不会被执行。所以,该方法一般只会书写在Configure方法末尾。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

UseMiddleWare()

将中间件封装,最终是使用Use注册

        //自定义中间件
        public class TestMiddelware
        {
            public RequestDelegate _next;

            public TestMiddelware(RequestDelegate next)
            {
                _next = next;
            }
            public Task Invoke(HttpContext context)
            {
                if (context.Request.Path.Value.Contains("1.jpg"))
                {
                    return context.Response.SendFileAsync("1.jpg");
                }

                if (_next != null)
                {
                    return _next(context);
                }
                throw new Exception("TestMiddelware error");
            }
        }
        app.UseMiddleware<TestMiddelware>(app, Array.Empty<object>());

参考:
https://www.cnblogs.com/xiaoxiaotank/p/15203811.html

posted @ 2020-02-04 04:51  .Neterr  阅读(1215)  评论(1编辑  收藏  举报