ASP.NET Core3.1 中间件middleware讲解

小小小朋友

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 RunMap 和 Use 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件 ,也叫中间件组件 。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件” ,因为它阻止中间件进一步处理请求。

将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。

使用 IApplicationBuilder 创建中间件管道

ASP.NET Core 请求管道包含一系列请求委托,依次调用。 下图演示了这一概念。 沿黑色箭头执行。

202003091959368695.png

每个委托均可在下一个委托前后执行操作。 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

尽可能简单的 ASP.NET Core 应用设置了处理所有请求的单个请求委托。 这种情况不包括实际请求管道。 调用单个匿名函数以响应每个 HTTP 请求。

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

用 Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不 调用 next 参数使管道短路。 通常可在下一个委托前后执行操作,如以下示例所示:

public class Startup
{public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) => {// Do work that doesn't write to the Response.
            await next.Invoke();// Do logging or other work that doesn't write to the Response.
        });
 
        app.Run(async context => {await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

当委托不将请求传递给下一个委托时,它被称为“让请求管道短路” 。 通常需要短路,因为这样可以避免不必要的工作。 例如,静态文件中间件可以处理对静态文件的请求,并让管道的其余部分短路,从而起到终端中间件 的作用。 如果中间件添加到管道中,且位于终止进一步处理的中间件前,它们仍处理 next.Invoke 语句后面的代码。 不过,请参阅下面有关尝试对已发送的响应执行写入操作的警告。

警告

在向客户端发送响应后,请勿调用 next.Invoke。 响应启动后,针对 HttpResponse 的更改将引发异常。 例如,设置标头和状态代码更改将引发异常。 调用 next 后写入响应正文:

  • 可能导致违反协议。 例如,写入的长度超过规定的 Content-Length
  • 可能损坏正文格式。 例如,向 CSS 文件中写入 HTML 页脚。

HasStarted 是一个有用的提示,指示是否已发送标头或已写入正文。

Run 委托不会收到 next 参数。 第一个 Run 委托始终为终端,用于终止管道。 Run 是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware] 方法:

public class Startup
{public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) => {// Do work that doesn't write to the Response.
            await next.Invoke();// Do logging or other work that doesn't write to the Response.
        });
 
        app.Run(async context => {await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

在前面的示例中,Run 委托将 "Hello from 2nd delegate." 写入响应,然后终止管道。 如果在 Run 委托之后添加了另一个 Use 或 Run 委托,则不会调用该委托。

中间件顺序

向 Startup.Configure 方法添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。

下面的 Startup.Configure 方法按照建议的顺序增加与安全相关的中间件组件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }else {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
 
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    // app.UseCookiePolicy();
 
    app.UseRouting();
    // app.UseRequestLocalization();// app.UseCors();
 
    app.UseAuthentication();
    app.UseAuthorization();
    // app.UseSession();
app.UseEndpoints(endpoints => {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

对中间件管道进行分支

Map 扩展用作约定来创建管道分支。 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>");
        });
    }
}

阅读 1.1k

从事软件开发11年

21 声望
1 粉丝
0 条评论

从事软件开发11年

21 声望
1 粉丝
文章目录
宣传栏