Migrar manipuladores e módulos HTTP para middleware do ASP.NET Core

Este artigo mostra como você pode migrar os módulos e manipuladores ASP.NET HTTP existentes do system.webserver para ASP.NET Core Middleware.

Módulos e manipuladores revisitados

Antes de prosseguir para o Middleware ASP.NET Core, vamos primeiro recapitular como funcionam os módulos e manipuladores HTTP:

Módulos Manipuladores

Os Manipuladores são:

  • Classes que implementam IHttpHandler

  • Usadas para manipular solicitações com um determinado nome de arquivo ou extensão, como .report

  • Configurado no Web.config

Os Módulos são:

  • Classes que implementam IHttpModule

  • Invocados para cada solicitação

  • Capaz de interromper rapidamente (parar o processamento adicional de uma solicitação)

  • Capazes de adicionar à resposta HTTP ou criar suas próprias respostas

  • Configurado no Web.config

A ordem em que os módulos processam as solicitações recebidas é determinada por:

  1. Uma série de eventos disparados pelo ASP.NET, como BeginRequest e AuthenticateRequest. Para obter uma lista completa, consulte System.Web.HttpApplication. Cada módulo pode criar um manipulador para um ou mais eventos.

  2. Para o mesmo evento, a ordem em que eles são configurados em Web.config.

Além dos módulos, você pode adicionar manipuladores para os eventos do ciclo de vida ao seu arquivo Global.asax.cs. Esses manipuladores são executados após os manipuladores nos módulos configurados.

De manipuladores e módulos a Middleware

O Middleware é mais simples do que os módulos e manipuladores HTTP:

  • Módulos, manipuladores, Global.asax.cs, Web.config (exceto a configuração do IIS) e o ciclo de vida do aplicativo desapareceram

  • As funções dos módulos e dos manipuladores foram assumidas pelo Middleware

  • O Middleware é configurado usando código, e não no Web.config

  • As ramificações do pipeline permitem que você envie solicitações a Middlewares específicos, com base não apenas na URL, mas também nos cabeçalhos da solicitação, cadeias de caracteres de consulta etc.
  • As ramificações do pipeline permitem que você envie solicitações a Middlewares específicos, com base não apenas na URL, mas também nos cabeçalhos da solicitação, cadeias de caracteres de consulta etc.

Os Middlewares são muito semelhantes aos módulos:

O Middleware e os módulos são processados em uma ordem diferente:

O Middleware de Autorização interrompe rapidamente uma solicitação para um usuário não autorizado. Uma solicitação para a página Inicial é permitida e processada pelo Middleware MVC. Uma solicitação para um relatório de vendas é permitida e processada por um Middleware personalizado de relatórios.

Observe como, na imagem acima, o Middleware de autenticação interrompeu rapidamente a solicitação.

Migrando o código do módulo para o Middleware

Um módulo HTTP existente terá uma aparência semelhante a esta:

// ASP.NET 4 module

using System;
using System.Web;

namespace MyApp.Modules
{
    public class MyModule : IHttpModule
    {
        public void Dispose()
        {
        }

        public void Init(HttpApplication application)
        {
            application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
            application.EndRequest += (new EventHandler(this.Application_EndRequest));
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            HttpContext context = ((HttpApplication)source).Context;

            // Do something with context near the beginning of request processing.
        }

        private void Application_EndRequest(Object source, EventArgs e)
        {
            HttpContext context = ((HttpApplication)source).Context;

            // Do something with context near the end of request processing.
        }
    }
}

Conforme mostrado na página Middleware, um Middleware ASP.NET Core é uma classe que expõe um método Invoke que recebe um HttpContext e retorna um Task. Seu novo Middleware terá a seguinte aparência:

// ASP.NET Core middleware

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

namespace MyApp.Middleware
{
    public class MyMiddleware
    {
        private readonly RequestDelegate _next;

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

        public async Task Invoke(HttpContext context)
        {
            // Do something with context near the beginning of request processing.

            await _next.Invoke(context);

            // Clean up.
        }
    }

    public static class MyMiddlewareExtensions
    {
        public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyMiddleware>();
        }
    }
}

O modelo de Middleware anterior foi retirado da seção sobre gravação do Middleware.

A classe auxiliar MyMiddlewareExtensions facilita a configuração do Middleware na sua classe Startup. O método UseMyMiddleware adiciona sua classe de Middleware ao pipeline de solicitações. Os serviços exigidos pelo Middleware são injetados no construtor do Middleware.

Seu módulo pode encerrar uma solicitação, por exemplo, se o usuário não estiver autorizado:

// ASP.NET 4 module that may terminate the request

private void Application_BeginRequest(Object source, EventArgs e)
{
    HttpContext context = ((HttpApplication)source).Context;

    // Do something with context near the beginning of request processing.

    if (TerminateRequest())
    {
        context.Response.End();
        return;
    }
}

Um Middleware lida com isso não chamando Invoke no próximo Middleware do pipeline. Lembre-se de que isso não encerra totalmente a solicitação, pois os Middlewares anteriores ainda serão chamados quando a resposta retornar pelo pipeline.

// ASP.NET Core middleware that may terminate the request

public async Task Invoke(HttpContext context)
{
    // Do something with context near the beginning of request processing.

    if (!TerminateRequest())
        await _next.Invoke(context);

    // Clean up.
}

Ao migrar a funcionalidade do seu módulo para o novo Middleware, você pode descobrir que seu código não está sendo compilado porque a classe HttpContext foi alterada significativamente no ASP.NET Core. Mais adiante, você verá como migrar para o novo ASP.NET Core HttpContext.

Migrando a inserção do módulo no pipeline de solicitação

Normalmente, os módulos HTTP são adicionados ao pipeline de solicitação usando Web.config:

<?xml version="1.0" encoding="utf-8"?>
<!--ASP.NET 4 web.config-->
<configuration>
  <system.webServer>
    <modules>
      <add name="MyModule" type="MyApp.Modules.MyModule"/>
    </modules>
  </system.webServer>
</configuration>

Converta isso adicionando seu novo Middleware ao pipeline de solicitações na sua classe Startup:

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?}");
    });
}

O ponto exato no pipeline no qual você insere seu novo Middleware depende do evento que ele lidou como um módulo (BeginRequest, EndRequest etc.) e da ordem dele na sua lista de módulos no Web.config.

Como dito anteriormente, não há ciclo de vida do aplicativo no ASP.NET Core e a ordem em que as respostas são processadas pelo Middleware difere da ordem usada pelos módulos. Isso pode tornar a sua decisão de ordenação mais desafiadora.

Se a ordenação se tornar um problema, você poderá dividir seu módulo em vários componentes de Middleware que podem ser ordenados de forma independente.

Migrando o código do manipulador para o Middleware

Um manipulador HTTP é mais ou menos assim:

// 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";
        }
    }
}

No seu projeto ASP.NET Core, você traduziria isso para um Middleware semelhante a este:

// 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>();
        }
    }
}

Esse Middleware é muito semelhante ao Middleware correspondente aos módulos. A única diferença real é que aqui não há chamada para _next.Invoke(context). Isso faz sentido, porque o manipulador está no final do pipeline de solicitações, de modo que não haverá um próximo Middleware a ser invocado.

Migrando a inserção do manipulador no pipeline de solicitação

A configuração de um manipulador HTTP é feita no Web.config e tem a seguinte aparência:

<?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>

Você poderia converter isso adicionando seu novo Middleware manipulador ao pipeline de solicitações em sua classe Startup, semelhante ao Middleware convertido de módulos. O problema com essa abordagem é que você enviaria todas as solicitações ao seu novo Middleware manipulador. No entanto, você só quer que as solicitações com uma determinada extensão cheguem ao seu Middleware. Isso lhe daria a você mesma funcionalidade que tinha com o seu manipulador HTTP.

Uma solução é ramificar o pipeline para solicitações com uma determinada extensão, utilizando o método de extensão MapWhen. Você faz isso no mesmo método Configure no qual adiciona o outro Middleware:

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 recebe esses parâmetros:

  1. Um lambda que recebe o HttpContext e retorna true se a solicitação deve descer pela ramificação. Isso significa que você pode ramificar solicitações não apenas com base na extensão, mas também em cabeçalhos de solicitação, parâmetros de cadeia de caracteres de consulta etc.

  2. Um lambda que recebe IApplicationBuilder e adiciona todo o Middleware para a ramificação. Isso significa que você pode adicionar outros Middlewares à ramificação na frente do Middleware do manipulador.

O Middleware adicionado ao pipeline antes da ramificação será chamado em todas as solicitações; a ramificação não terá impacto sobre eles.

Carregamento das opções de Middleware usando o padrão de opções

Alguns módulos e manipuladores têm as opções de configuração armazenadas no Web.config. No entanto, no ASP.NET Core, um novo modelo de configuração é usado no lugar do Web.config.

O novo sistema de configuração oferece a você essas opções para resolver isso:

  1. Crie uma classe para manter as opções do Middleware, por exemplo:

    public class MyMiddlewareOptions
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
    }
    
  2. Armazenar os valores das opções

    O sistema de configuração permite que você armazene os valores das opções onde quiser. No entanto, a maioria dos sites usa appsettings.json, portanto, adotaremos essa abordagem:

    {
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    

    MyMiddlewareOptionsSection aqui está o nome da seção. Ele não precisa ser o mesmo que o nome da classe de opções.

  3. Associar os valores da opção à classe de opções

    O padrão de opções usa a estrutura de injeção de dependências do ASP.NET Core para associar o tipo de opções (como MyMiddlewareOptions) a um objeto MyMiddlewareOptions que tem as opções reais.

    Atualize sua classe Startup:

    1. Se você estiver usando appsettings.json, adicione-a ao construtor de configurações no construtor Startup:

      public Startup(IHostingEnvironment env)
      {
          var builder = new ConfigurationBuilder()
              .SetBasePath(env.ContentRootPath)
              .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
              .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
              .AddEnvironmentVariables();
          Configuration = builder.Build();
      }
      
    2. Configure o serviço de opções:

      public void ConfigureServices(IServiceCollection services)
      {
          // Setup options service
          services.AddOptions();
      
          // Load options from section "MyMiddlewareOptionsSection"
          services.Configure<MyMiddlewareOptions>(
              Configuration.GetSection("MyMiddlewareOptionsSection"));
      
          // Add framework services.
          services.AddMvc();
      }
      
    3. Associe suas opções à sua classe de opções:

      public void ConfigureServices(IServiceCollection services)
      {
          // Setup options service
          services.AddOptions();
      
          // Load options from section "MyMiddlewareOptionsSection"
          services.Configure<MyMiddlewareOptions>(
              Configuration.GetSection("MyMiddlewareOptionsSection"));
      
          // Add framework services.
          services.AddMvc();
      }
      
  4. Injete as opções no seu construtor de Middleware. Isso é semelhante a injetar opções em um controlador.

    public class MyMiddlewareWithParams
    {
        private readonly RequestDelegate _next;
        private readonly MyMiddlewareOptions _myMiddlewareOptions;
    
        public MyMiddlewareWithParams(RequestDelegate next,
            IOptions<MyMiddlewareOptions> optionsAccessor)
        {
            _next = next;
            _myMiddlewareOptions = optionsAccessor.Value;
        }
    
        public async Task Invoke(HttpContext context)
        {
            // Do something with context near the beginning of request processing
            // using configuration in _myMiddlewareOptions
    
            await _next.Invoke(context);
    
            // Do something with context near the end of request processing
            // using configuration in _myMiddlewareOptions
        }
    }
    

    O método de extensão UseMiddleware que adiciona seu Middleware ao IApplicationBuilder cuida da injeção de dependências.

    Isso não se limita aos objetos IOptions. Qualquer outro objeto que seu Middleware exija pode ser injetado dessa maneira.

Carregando as opções do Middleware através da injeção direta

O padrão de opções tem a vantagem de criar um acoplamento flexível entre os valores das opções e seus consumidores. Depois que você associa uma classe de opções aos valores reais das opções, qualquer outra classe poderá obter acesso às opções através da estrutura de injeção de dependências. Não há necessidade de transmitir os valores das opções.

Entretanto, isso não funcionará se você quiser usar o mesmo Middleware duas vezes, com opções diferentes. Por exemplo, um Middleware de autorização usado em ramificações diferentes que permitem funções diferentes. Você não pode associar dois objetos de opções diferentes a uma única classe de opções.

A solução é obter os objetos de opções com os valores reais de opções em sua classe Startup e passá-los diretamente para cada instância do seu Middleware.

  1. Adicionar uma segunda chave a appsettings.json

    Para adicionar um segundo conjunto de opções ao arquivo appsettings.json, use uma nova chave para identificá-lo de forma exclusiva:

    {
      "MyMiddlewareOptionsSection2": {
        "Param1": "Param1Value2",
        "Param2": "Param2Value2"
      },
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    
  2. Recupere os valores das opções e passe-os para o Middleware. O método de extensão Use... (que adiciona seu Middleware ao pipeline) é um local lógico para você passar os valores das opções:

    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?}");
        });
    }
    
  3. Você pode habilitar o Middleware para receber um parâmetro de opções. Forneça uma sobrecarga do método de extensão Use... (que recebe o parâmetro de opções e o passa para UseMiddleware). Quando UseMiddleware é chamado com parâmetros, ele passa os parâmetros para o construtor do Middleware quando ele criar uma instância o objeto de Middleware.

    public static class MyMiddlewareWithParamsExtensions
    {
        public static IApplicationBuilder UseMyMiddlewareWithParams(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyMiddlewareWithParams>();
        }
    
        public static IApplicationBuilder UseMyMiddlewareWithParams(
            this IApplicationBuilder builder, MyMiddlewareOptions myMiddlewareOptions)
        {
            return builder.UseMiddleware<MyMiddlewareWithParams>(
                new OptionsWrapper<MyMiddlewareOptions>(myMiddlewareOptions));
        }
    }
    

    Observe como isso encapsula o objeto de opções em um objeto OptionsWrapper. Isso implementa IOptions, conforme esperado pelo construtor do Middleware.

Migrando para o novo HttpContext

Você viu anteriormente que o método Invoke no seu Middleware recebe um parâmetro do tipo HttpContext:

public async Task Invoke(HttpContext context)

HttpContext foi alterado significativamente no ASP.NET Core. Esta seção mostra como traduzir as propriedades mais comumente usadas de System.Web.HttpContext para o novo Microsoft.AspNetCore.Http.HttpContext.

HttpContext

HttpContext.Items é traduzido para:

IDictionary<object, object> items = httpContext.Items;

ID de solicitação exclusiva (sem equivalente em System.Web.HttpContext)

Fornece a você uma ID exclusiva para cada solicitação. Muito útil para você incluir nos seus logs.

string requestId = httpContext.TraceIdentifier;

HttpContext.Request

HttpContext.Request.HttpMethod é traduzido para:

string httpMethod = httpContext.Request.Method;

HttpContext.Request.QueryString é traduzido para:

IQueryCollection queryParameters = httpContext.Request.Query;

// If no query parameter "key" used, values will have 0 items
// If single value used for a key (...?key=v1), values will have 1 item ("v1")
// If key has multiple values (...?key=v1&key=v2), values will have 2 items ("v1" and "v2")
IList<string> values = queryParameters["key"];

// If no query parameter "key" used, value will be ""
// If single value used for a key (...?key=v1), value will be "v1"
// If key has multiple values (...?key=v1&key=v2), value will be "v1,v2"
string value = queryParameters["key"].ToString();

HttpContext.Request.Url e HttpContext.Request.RawUrl são traduzidos para:

// using Microsoft.AspNetCore.Http.Extensions;
var url = httpContext.Request.GetDisplayUrl();

HttpContext.Request.IsSecureConnection é traduzido para:

var isSecureConnection = httpContext.Request.IsHttps;

HttpContext.Request.UserHostAddress é traduzido para:

var userHostAddress = httpContext.Connection.RemoteIpAddress?.ToString();

HttpContext.Request.Cookies é traduzido para:

IRequestCookieCollection cookies = httpContext.Request.Cookies;
string unknownCookieValue = cookies["unknownCookie"]; // will be null (no exception)
string knownCookieValue = cookies["cookie1name"];     // will be actual value

HttpContext.Request.RequestContext.RouteData é traduzido para:

var routeValue = httpContext.GetRouteValue("key");

HttpContext.Request.Headers é traduzido para:

// using Microsoft.AspNetCore.Http.Headers;
// using Microsoft.Net.Http.Headers;

IHeaderDictionary headersDictionary = httpContext.Request.Headers;

// GetTypedHeaders extension method provides strongly typed access to many headers
var requestHeaders = httpContext.Request.GetTypedHeaders();
CacheControlHeaderValue cacheControlHeaderValue = requestHeaders.CacheControl;

// For unknown header, unknownheaderValues has zero items and unknownheaderValue is ""
IList<string> unknownheaderValues = headersDictionary["unknownheader"];
string unknownheaderValue = headersDictionary["unknownheader"].ToString();

// For known header, knownheaderValues has 1 item and knownheaderValue is the value
IList<string> knownheaderValues = headersDictionary[HeaderNames.AcceptLanguage];
string knownheaderValue = headersDictionary[HeaderNames.AcceptLanguage].ToString();

HttpContext.Request.UserAgent é traduzido para:

string userAgent = headersDictionary[HeaderNames.UserAgent].ToString();

HttpContext.Request.UrlReferrer é traduzido para:

string urlReferrer = headersDictionary[HeaderNames.Referer].ToString();

HttpContext.Request.ContentType é traduzido para:

// using Microsoft.Net.Http.Headers;

MediaTypeHeaderValue mediaHeaderValue = requestHeaders.ContentType;
string contentType = mediaHeaderValue?.MediaType.ToString();   // ex. application/x-www-form-urlencoded
string contentMainType = mediaHeaderValue?.Type.ToString();    // ex. application
string contentSubType = mediaHeaderValue?.SubType.ToString();  // ex. x-www-form-urlencoded

System.Text.Encoding requestEncoding = mediaHeaderValue?.Encoding;

HttpContext.Request.Form é traduzido para:

if (httpContext.Request.HasFormContentType)
{
    IFormCollection form;

    form = httpContext.Request.Form; // sync
    // Or
    form = await httpContext.Request.ReadFormAsync(); // async

    string firstName = form["firstname"];
    string lastName = form["lastname"];
}

Aviso

A leitura dos valores do formulário é feita somente se o subtipo de conteúdo for x-www-form-urlencoded ou form-data.

HttpContext.Request.InputStream é traduzido para:

string inputBody;
using (var reader = new System.IO.StreamReader(
    httpContext.Request.Body, System.Text.Encoding.UTF8))
{
    inputBody = reader.ReadToEnd();
}

Aviso

Use esse código somente em um Middleware do tipo manipulador, no final de um pipeline.

Você pode fazer a leitura do corpo bruto, conforme mostrado acima, apenas uma vez por solicitação. O Middleware que tentar fazer a leitura do corpo após a primeira leitura lerá um corpo vazio.

Isso não se aplica à leitura de um formulário, conforme mostrado anteriormente, porque isso é feito a partir de um buffer.

HttpContext.Response

HttpContext.Response.Status e HttpContext.Response.StatusDescription são traduzidos para:

// using Microsoft.AspNetCore.Http;
httpContext.Response.StatusCode = StatusCodes.Status200OK;

HttpContext.Response.ContentEncoding e HttpContext.Response.ContentType são traduzidos para:

// using Microsoft.Net.Http.Headers;
var mediaType = new MediaTypeHeaderValue("application/json");
mediaType.Encoding = System.Text.Encoding.UTF8;
httpContext.Response.ContentType = mediaType.ToString();

HttpContext.Response.ContentType por si só também é traduzido para:

httpContext.Response.ContentType = "text/html";

HttpContext.Response.Output é traduzido para:

string responseContent = GetResponseContent();
await httpContext.Response.WriteAsync(responseContent);

HttpContext.Response.TransmitFile

O envio de um arquivo é discutido em Recursos de solicitação no ASP.NET Core.

HttpContext.Response.Headers

O envio de cabeçalhos de resposta é complicado pelo fato de que, se você os definir depois que algo tiver sido gravado no corpo da resposta, eles não serão enviados.

A solução é a definição de um método de retorno de chamada que será chamado logo antes do início da gravação na resposta. A melhor maneira de fazer isso é no início do método Invoke no seu Middleware. É esse método de retorno de chamada que define os cabeçalhos da resposta.

O seguinte código define um método de retorno de chamada chamado SetHeaders:

public async Task Invoke(HttpContext httpContext)
{
    // ...
    httpContext.Response.OnStarting(SetHeaders, state: httpContext);

O método de retorno de chamada SetHeaders teria a seguinte aparência:

// using Microsoft.AspNet.Http.Headers;
// using Microsoft.Net.Http.Headers;

private Task SetHeaders(object context)
{
    var httpContext = (HttpContext)context;

    // Set header with single value
    httpContext.Response.Headers["ResponseHeaderName"] = "headerValue";

    // Set header with multiple values
    string[] responseHeaderValues = new string[] { "headerValue1", "headerValue1" };
    httpContext.Response.Headers["ResponseHeaderName"] = responseHeaderValues;

    // Translating ASP.NET 4's HttpContext.Response.RedirectLocation  
    httpContext.Response.Headers[HeaderNames.Location] = "http://www.example.com";
    // Or
    httpContext.Response.Redirect("http://www.example.com");

    // GetTypedHeaders extension method provides strongly typed access to many headers
    var responseHeaders = httpContext.Response.GetTypedHeaders();

    // Translating ASP.NET 4's HttpContext.Response.CacheControl 
    responseHeaders.CacheControl = new CacheControlHeaderValue
    {
        MaxAge = new System.TimeSpan(365, 0, 0, 0)
        // Many more properties available 
    };

    // If you use .NET Framework 4.6+, Task.CompletedTask will be a bit faster
    return Task.FromResult(0);
}

HttpContext.Response.Cookies

Cookies viajam para o navegador em um cabeçalho de resposta Set-Cookie. Como resultado, o envio de cookies exige a mesma chamada de retorno usada para o envio de cabeçalhos de resposta:

public async Task Invoke(HttpContext httpContext)
{
    // ...
    httpContext.Response.OnStarting(SetCookies, state: httpContext);
    httpContext.Response.OnStarting(SetHeaders, state: httpContext);

O método de retorno de chamada SetCookies seria parecido com o seguinte:

private Task SetCookies(object context)
{
    var httpContext = (HttpContext)context;

    IResponseCookies responseCookies = httpContext.Response.Cookies;

    responseCookies.Append("cookie1name", "cookie1value");
    responseCookies.Append("cookie2name", "cookie2value",
        new CookieOptions { Expires = System.DateTime.Now.AddDays(5), HttpOnly = true });

    // If you use .NET Framework 4.6+, Task.CompletedTask will be a bit faster
    return Task.FromResult(0); 
}

Recursos adicionais