.net5 core webapi进阶之五:自定义中间件的使用

一、什么是中间件?先看看微软官方文档对中间件的定义:

官网地址:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0

官网还给出了一张中间件调用的示意图,如下:

简而言之,我们可以把中间件理解成一个功能,它可以对请求进行处理,

并决定是将处理过的请求传递给下一个中间件继续处理还是结束传递,直接发送一个响应给客户端。

 

二、如果要自定义一个中间件,在请求到达自定义的中间件之前会不会有其他中间件进行处理?

如果有,是哪些中间件,其顺序是怎么样的?

使用VS2019新建一个WebApi项目后,系统会默认添加一些中间件,比如 app.UseRouting( ); 、

app.UseAuthorization( ); 、app.UseEndpoints( )等(一般是app.UseXxxxx( )这样的形式),

它们位于 Startup.cs 的 Configure(IApplicationBuilder app, IWebHostEnvironment env) 方法中,

其执行顺序即是添加的顺序。对于这些默认的中间件我们暂时不去深究,大概的执行流程

可以参考官方文档中ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道示意图,如下:

 

三、实现一个自定义的中间件。

功能:客户端请求应用程序的一张图片,自定义中间件根据客户端身份信息决定是返回此图片还是返回一张 "没有授权" 的图片。

使用场景:WebApi 给不同的客户端提供服务,不同的客户端有自己的图片文件夹,比如 A公司对应的图片文件夹是 A_Images,

B公司对应的图片文件夹是B_Images,本公司内部管理员对应的图片文件夹是Admin_Images等,

此功能是防止A公司发送的URL错写成B公司的从而造成B公司的图片信息泄露。

扩展:此功能应用到网站中可以稍加修改设计成图片防盗链的功能。

客户端请求的图片如下(lion.jpg):

如果是没授权的客户端,响应如下的一张图片(hellokitty.png):

 

四、实现步骤。

第1步,预备工作:

新建一个新的 .NET CORE WEBAPI 项目名叫 webapidemo3,

按惯例先删除项目默认生成的 WeatherForecast.cs 和 WeatherForecastController.cs,

在Controllers文件夹下新增一个ImagesController的控制器,在此控制器中新增终结点 Demo1( ),代码如下:

namespace webapidemo3
{
    [Route("api/[controller]")]
    [ApiController]
    public class ImagesController : ControllerBase
    {

        [HttpGet]
        [Route("demo1")]
        public IActionResult Demo1()
        {
            return Ok("Test...");
        }

    }
}

第2步,在VS2019解决方案中新建一个 Middlewares 的文件夹,

在里面新增一个名为 ImageRequestMiddleware.cs 的中间件类,如下:

新增 ImageRequestMiddleware.cs 后的默认代码如下:

namespace webapidemo3
{
    // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
    public class ImageRequestMiddleware
    {
        private readonly RequestDelegate _next;

        public ImageRequestMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext httpContext)
        {

            return _next(httpContext);
        }
    }

    // Extension method used to add the middleware to the HTTP request pipeline.
    public static class ImageRequestMiddlewareExtensions
    {
        public static IApplicationBuilder UseImageRequestMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ImageRequestMiddleware>();
        }
    }
}

要完成的功能需要在 "public Task Invoke(HttpContext httpContext)" 这个函数中进行编码。

类 "public static class ImageRequestMiddlewareExtensions" 的作用是

提供一个扩展方法将这个中间件注册到 HTTP 请求管道中,具体就是在 Startup.cs 的

Configure(IApplicationBuilder app, IWebHostEnvironment env) 方法中加入如下一行代码(见红色部分):

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();

            app.UseImageRequestMiddleware();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

注意:中间件的执行是有顺序的,我们自定义的中间件放在客户端经过认证后(app.UseAuthorization( );),

终结点执行前(app.UseEndpoints( )),实际项目中根据需要更改位置。

第3步,编码实现,代码如下。

    public class ImageRequestMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly string _wwwrootPath;
        public ImageRequestMiddleware(RequestDelegate next, IWebHostEnvironment env)
        {
            _next = next;

            //用构造函数注入的方式获取应用程序WebRoot目录的物理地址,即wwwroot文件夹的路径(包含wwwroot)
            _wwwrootPath = env.WebRootPath;
        }

        public Task Invoke(HttpContext httpContext)
        {
            //因为此中间件是在认证中间件之后执行的,
            //所以companyId应该从客户端传递的认证信息中解析出来
            //具体的获取方式和项目的身份认证方式相关,以什么方式写入就以什么方式取出
            //如果是cookie认证,可能的取值方式如下
            //string companyId = httpContext.Request.Cookies["companyId"];

            string companyId = "A1234"; //假设从客户端解析出来的companyId=A1234
            string authorizedImgUrl = "img/" + companyId; //客户端有权限访问的图片路径
              
            string url = httpContext.Request.Path;//客户端访问的网址,

            //只处理图片,如果访问网址不包含图片则直接进入下一个中间件的处理
            //这里根据实际情况可以加入.png/.jpeg/.gif等图片格式的判断
            if (!url.EndsWith(".jpg", true, CultureInfo.CurrentCulture))
            {
                return _next(httpContext);
            }

            string imgPath;
            if (url.IndexOf(authorizedImgUrl) > 0) //url中图片路径和授权访问的图片路径一致
            {
                imgPath = url.Substring(url.IndexOf(authorizedImgUrl)); 
            }
            else //url中图片路径和授权访问的图片路径不一致就响应指定的图片
            {
                imgPath = "img/hellokitty.png";
            }

            //输出图片文件
            httpContext.Response.SendFileAsync(Path.Combine(_wwwrootPath, imgPath));

            //中间件短路,不继续执行下一个中间件
            return null; 
        }
    }

注:中间件中如果有Response返回后,不会再向下传递了(即调用 Next( ) 无效)

 

五、测试。

准备工作:

1. 在项目根目录下新增 wwwroot 的文件夹,然后再新增如下的文件夹和文件:

注意:wwwroot这个文件夹名称有特殊的含义,这里通常存放image、js、css等资源,

在网址中访问其中的图片的时候是看不到 wwwroot 这个路径的。

2. 在 Startup.cs 的 Configure( ) 方法中注册自定义的中间件,如下(见红色部分代码)

注意添加的位置,实际项目中如果顺序不恰当可能达不到我们要的效果:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();

            app.UseImageRequestMiddleware();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }    

3. 打开浏览器,我们先访问终结点 Demo1() 看看会不会错误的把图片显示出来。

是我们期望的结果。

4. 再访问网址: http://localhost:51630/img/A1234/lion.jpg  ,结果如下:

有 "img/A1234" 这个路径的权限,可以正常得到图片内容。

5. 最后访问网址: http://localhost:51630/img/B5678/lion.jpg  ,结果如下:

没有 "img/B5678" 这个路径的权限,输出指定的图片,达到效果。

 

posted @ 2021-02-28 13:09  屏风马  阅读(1979)  评论(0编辑  收藏  举报