将 HTTP 处理程序迁移到 ASP.NET 核心中间件

本文介绍如何将现有 ASP.NET HTTP 处理程序从 system.webserver 迁移到 ASP.NET Core 中间件

处理程序再探讨

在继续 ASP.NET 核心中间件之前,让我们先回顾一下 HTTP 处理程序的工作原理:

模块处理程序

处理程序是:

  • 实现 IHttpHandler 的类

  • 用于处理具有给定文件名或扩展名的请求,例如 .report

  • Web.config配置

从处理程序到中间件

中间件比 HTTP 处理程序更简单:

  • 处理程序、Web.config(IIS 配置除外)和应用程序生命周期都消失了

  • 中间件接管了处理程序的角色

  • 中间件是使用代码而不是在 Web.config 中配置的

  • 通过管道分支 ,可以基于 URL 以及请求标头、查询字符串等将请求发送到特定中间件。
  • 通过管道分支 ,可以基于 URL 以及请求标头、查询字符串等将请求发送到特定中间件。

中间件与处理程序非常相似:

  • 能够创建自己的 HTTP 响应

授权中间件对未授权的用户的请求进行短路。MVC 中间件允许和处理索引页的请求。自定义报表中间件允许和处理销售报表的请求。

将处理程序代码迁移到中间件

HTTP 处理程序如下所示:

// ASP.NET 4 handler

using System.Web;

namespace MyApp.HttpHandlers
{
    public class MyHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string response = GenerateResponse(context);

            context.Response.ContentType = GetContentType();
            context.Response.Output.Write(response);
        }

        // ...

        private string GenerateResponse(HttpContext context)
        {
            string title = context.Request.QueryString["title"];
            return string.Format("Title of the report: {0}", title);
        }

        private string GetContentType()
        {
            return "text/plain";
        }
    }
}

在 ASP.NET Core 项目中,你将将其转换为如下所示的中间件:

// ASP.NET Core middleware migrated from a handler

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MyApp.Middleware
{
    public class MyHandlerMiddleware
    {

        // Must have constructor with this signature, otherwise exception at run time
        public MyHandlerMiddleware(RequestDelegate next)
        {
            // This is an HTTP Handler, so no need to store next
        }

        public async Task Invoke(HttpContext context)
        {
            string response = GenerateResponse(context);

            context.Response.ContentType = GetContentType();
            await context.Response.WriteAsync(response);
        }

        // ...

        private string GenerateResponse(HttpContext context)
        {
            string title = context.Request.Query["title"];
            return string.Format("Title of the report: {0}", title);
        }

        private string GetContentType()
        {
            return "text/plain";
        }
    }

    public static class MyHandlerExtensions
    {
        public static IApplicationBuilder UseMyHandler(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyHandlerMiddleware>();
        }
    }
}

此中间件与与模块对应的中间件非常相似。 唯一真正的区别是这里没有调用 _next.Invoke(context)。 这很有意义,因为处理程序位于请求管道的末尾,因此不会有下一个要调用的中间件。

将处理程序插入迁移到请求管道中

配置 HTTP 处理程序是在 Web.config 完成的,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<!--ASP.NET 4 web.config-->
<configuration>
  <system.webServer>
    <handlers>
      <add name="MyHandler" verb="*" path="*.report" type="MyApp.HttpHandlers.MyHandler" resourceType="Unspecified" preCondition="integratedMode"/>
    </handlers>
  </system.webServer>
</configuration>

可以通过在 Startup 类中将新处理程序中间件添加到请求管道来对此进行转换,类似于从模块转换的中间件。 此方法的问题是,它会将所有请求发送到新的处理程序中间件。 但是,你仅希望具有给定扩展名的请求到达中间件。 这将为你提供与 HTTP 处理程序相同的功能。

一种解决方案是使用 MapWhen 扩展方法为具有给定扩展名的请求对管道进行分支。 在添加其他中间件的相同 Configure 方法中执行此操作:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMyMiddleware();

    app.UseMyMiddlewareWithParams();

    var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
    var myMiddlewareOptions2 = Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
    app.UseMyMiddlewareWithParams(myMiddlewareOptions);
    app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

    app.UseMyTerminatingMiddleware();

    // Create branch to the MyHandlerMiddleware. 
    // All requests ending in .report will follow this branch.
    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".report"),
        appBranch => {
            // ... optionally add more middleware to this branch
            appBranch.UseMyHandler();
        });

    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".context"),
        appBranch => {
            appBranch.UseHttpContextDemoMiddleware();
        });

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

MapWhen 采用以下参数:

  1. 一个 lambda,它采用 HttpContext,并在请求应沿着分支向下前进时返回 true。 这意味着,您不仅可以基于请求的扩展名进行分支,还可以根据请求头、查询字符串参数等进行分支。

  2. 一个 lambda,它采用 IApplicationBuilder 并为分支添加所有中间件。 这意味着可以将其他中间件添加到处理程序中间件前面的分支。

中间件在对所有请求调用分支之前添加到管道;分支对它们没有影响。

有关更多详细信息,请参阅 中间件文档 ,了解使用中间件替换处理程序用法的其他方法。

迁移到新 HttpContext

Invoke中间件中的方法接受一个类型为HttpContext的参数。

public async Task Invoke(HttpContext context)

HttpContext 在 ASP.NET Core 中发生了重大变化。 有关如何将最常用的System.Web.HttpContext属性翻译到新的Microsoft.AspNetCore.Http.HttpContext属性的详细信息,请参阅从 ASP.NET Framework HttpContext 迁移到 ASP.NET Core

其他资源