Migración de controladores y módulos HTTP a middleware de ASP.NET Core

En este artículo se muestra cómo migrar módulos y controladores HTTP de ASP.NET existentes de system.webserver a middleware de ASP.NET Core.

Módulos y controladores revisados

Antes de continuar con middleware de ASP.NET Core, vamos a resumir primero cómo funcionan los módulos y controladores HTTP:

Controlador de módulos

Los controladores son:

  • Clases que implementan IHttpHandler

  • Se usan para controlar las solicitudes con un nombre de archivo o una extensión dados, como .report

  • Configurados en Web.config

Los módulos son:

  • Clases que implementan IHttpModule

  • Se invocan para cada solicitud

  • Capaces de producir un cortocircuito (detener el procesamiento adicional de una solicitud)

  • Pueden agregar a la respuesta HTTP o crear su propia respuesta

  • Configurados en Web.config

El orden en que los módulos procesan las solicitudes entrantes viene determinado por:

  1. Eventos de serie desencadenados por ASP.NET, como BeginRequest y AuthenticateRequest. Para obtener una lista completa, vea System.Web.HttpApplication. Cada módulo puede crear un controlador para uno o varios eventos.

  2. Para el mismo evento, el orden en que están configurados en Web.config.

Además de los módulos, puede agregar controladores para los eventos del ciclo de vida al archivo Global.asax.cs. Estos controladores se ejecutan después de los controladores en los módulos configurados.

Desde controladores y módulos a middleware

El middleware es más sencillo que los módulos y controladores HTTP:

  • Los módulos, los controladores, Global.asax.cs, Web.config (excepto la configuración de IIS) y el ciclo de vida de la aplicación han desaparecido

  • El middleware ha tomado los roles de los módulos y los controladores

  • El middleware se configura mediante código en lugar de en Web.config

  • La bifurcación de canalización permite enviar solicitudes a middleware específico, en función de no solo la dirección URL, sino también de los encabezados de solicitud, cadenas de consulta, etc.
  • La bifurcación de canalización permite enviar solicitudes a middleware específico, en función de no solo la dirección URL, sino también de los encabezados de solicitud, cadenas de consulta, etc.

El middleware es muy similar a los módulos:

El middleware y los módulos se procesan en un orden diferente:

El middleware de autorización produce un cortocircuito en una solicitud para un usuario que no está autorizado. MVC Middleware permite y procesa una solicitud para la página de Índice. Un informe personalizado de middleware permite y procesa una solicitud para un informe de ventas.

Observe cómo en la imagen anterior, el middleware de autenticación ha producido un cortocircuito en la solicitud.

Migración del código del módulo al middleware

Un módulo HTTP existente tendrá un aspecto similar al siguiente:

// 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.
        }
    }
}

Como se muestra en la página Middleware, un middleware de ASP.NET Core es una clase que expone un método Invoke que toma HttpContext y devuelve Task. El nuevo middleware tendrá este aspecto:

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

La plantilla de middleware anterior se tomó de la sección sobre escritura de middleware.

El asistente MyMiddlewareExtensions facilita la configuración del middleware en la clase Startup. El método UseMyMiddleware agrega la clase de middleware a la canalización de solicitudes. Los servicios requeridos por el middleware se insertan en el constructor del middleware.

El módulo podría finalizar una solicitud, por ejemplo, si el usuario no está 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;
    }
}

Un middleware controla esto sin llamar a Invoke en el siguiente middleware de la canalización. Tenga en cuenta que esto no finaliza completamente la solicitud, ya que los middleware anteriores se seguirán invocando cuando la respuesta vuelva a través de la canalización.

// 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.
}

Al migrar la funcionalidad del módulo al nuevo middleware, es posible que el código no se compile porque la clase HttpContext ha cambiado significativamente en ASP.NET Core. Más adelante, verá cómo migrar al nuevo HttpContext de ASP.NET Core.

Migración de la inserción de módulos en la canalización de solicitudes

Normalmente, los módulos HTTP se agregan a la canalización de solicitudes mediante 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>

Para convertir esto, agregue el nuevo middleware a la canalización de solicitudes en la clase 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?}");
    });
}

El punto exacto de la canalización donde se inserta el nuevo middleware depende del evento que controló como un módulo (BeginRequest, EndRequest, etc.) y su orden en la lista de módulos en Web.config.

Como se indicó anteriormente, no hay ningún ciclo de vida de la aplicación en ASP.NET Core y el orden en el que el middleware procesa las respuestas difiere del orden utilizado por los módulos. Esto podría hacer que la decisión de ordenación sea más difícil.

Si el orden se convierte en un problema, puede dividir el módulo en varios componentes de middleware que se pueden ordenar de forma independiente.

Migración del código del controlador al middleware

Un controlador HTTP tiene un aspecto similar al siguiente:

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

En el proyecto de ASP.NET Core, lo traduciría a un middleware similar al siguiente:

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

Este middleware es muy similar al middleware correspondiente a los módulos. La única diferencia real es que aquí no hay ninguna llamada a _next.Invoke(context). Eso tiene sentido, ya que el controlador está al final de la canalización de solicitudes, por lo que no habrá ningún middleware siguiente que se invoque.

Migración de la inserción del controlador a la canalización de solicitudes

La configuración de un controlador HTTP se realiza en Web.config y tiene un aspecto similar al siguiente:

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

Para convertir esto, agregue el nuevo middleware de controlador a la canalización de solicitudes en la clase Startup, similar al middleware convertido desde módulos. El problema con ese enfoque es que enviaría todas las solicitudes al nuevo middleware del controlador. Sin embargo, solo quiere que las solicitudes con una extensión determinada lleguen al middleware. Esto le proporcionaría la misma funcionalidad que tenía con el controlador HTTP.

Una solución consiste en bifurcar la canalización para las solicitudes con una extensión determinada, mediante el método de extensión MapWhen. Esto se hace en el mismo método Configure en el que se agrega el otro 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 toma estos parámetros:

  1. Una expresión lambda que toma HttpContext y devuelve true si la solicitud debe bajar en la rama. Esto significa que puede bifurcar solicitudes no solo en función de su extensión, sino también en función de encabezados de solicitud, parámetros de cadena de consulta, etc.

  2. Una expresión lambda que toma IApplicationBuilder y agrega todo el middleware para la rama. Esto significa que puede agregar middleware adicional a la rama delante del middleware del controlador.

El middleware agregado a la canalización antes de que se invoque la rama en todas las solicitudes; la rama no tendrá ningún impacto en ellas.

Carga de opciones de middleware mediante el patrón de opciones

Algunos módulos y controladores tienen opciones de configuración que se almacenan en Web.config. Sin embargo, en ASP.NET Core se usa un nuevo modelo de configuración en lugar de Web.config.

El nuevo sistema de configuración ofrece estas opciones para resolver esto:

  1. Cree una clase para contener las opciones de middleware, por ejemplo:

    public class MyMiddlewareOptions
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
    }
    
  2. Almacenamiento de los valores de opción

    El sistema de configuración permite almacenar valores de opción en cualquier lugar que desee. Sin embargo, la mayoría de los sitios usan appsettings.json, por lo que tomaremos ese enfoque:

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

    MyMiddlewareOptionsSection aquí es un nombre de sección. No es necesario que sea el mismo que el nombre de la clase de opciones.

  3. Asocie los valores de opción a la clase de opciones

    El patrón de opciones usa el marco de inserción de dependencias de ASP.NET Core para asociar el tipo de opciones (como MyMiddlewareOptions) a un objeto MyMiddlewareOptions que tiene las opciones reales.

    Actualice la clase Startup:

    1. Si usa appsettings.json, agréguela al generador de configuración en el constructor 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 el servicio de opciones:

      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. Asocie las opciones a la clase de opciones:

      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. Inserte las opciones en el constructor de middleware. Esto es similar a insertar opciones en un 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
        }
    }
    

    El método de extensión UseMiddleware que agrega el middleware a IApplicationBuilder se encarga de la inserción de dependencias.

    Esto no se limita a los objetos IOptions. Cualquier otro objeto que requiera el middleware se puede insertar de esta manera.

Carga de opciones de middleware a través de la inserción directa

El patrón de opciones tiene la ventaja de que crea un acoplamiento flexible entre los valores de las opciones y sus consumidores. Una vez que haya asociado una clase de opciones con los valores de opciones reales, cualquier otra clase puede obtener acceso a las opciones a través del marco de inserción de dependencias. No es necesario pasar valores de opciones.

Esto se desglosa si desee usar el mismo middleware dos veces, con diferentes opciones. Por ejemplo, un middleware de autorización usado en diferentes ramas que permiten distintos roles. No se pueden asociar dos objetos de opciones diferentes con la clase de una opción.

La solución consiste en obtener los objetos de opciones con los valores de opciones reales en la clase Startup y pasarlos directamente a cada instancia del middleware.

  1. Adición de una segunda clave a appsettings.json

    Para agregar un segundo conjunto de opciones al archivo appsettings.json, use una nueva clave para identificarla de forma única:

    {
      "MyMiddlewareOptionsSection2": {
        "Param1": "Param1Value2",
        "Param2": "Param2Value2"
      },
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    
  2. Recupere los valores de opciones y páselos al middleware. El método de extensión Use... (que agrega el middleware a la canalización) es un lugar lógico para pasar los valores de opción:

    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. Habilite el middleware para tomar un parámetro de opciones. Proporcione una sobrecarga del método de extensión Use... (que toma el parámetro de opciones y lo pasa a UseMiddleware). Cuando se llama a UseMiddleware con parámetros, pasa los parámetros al constructor de middleware cuando crea una instancia del 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));
        }
    }
    

    Tenga en cuenta cómo esto encapsula el objeto de opciones en un objeto OptionsWrapper. Esto implementa IOptions, según lo previsto por el constructor de middleware.

Migración al nuevo HttpContext

Ha visto anteriormente que el método Invoke del middleware toma un parámetro de tipo HttpContext:

public async Task Invoke(HttpContext context)

HttpContext ha cambiado significativamente en ASP.NET Core. En esta sección se muestra cómo traducir las propiedades más usadas de System.Web.HttpContext a la nueva Microsoft.AspNetCore.Http.HttpContext.

HttpContext

HttpContext.Items se traduce en:

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

Identificador de solicitud único (sin homólogo de System.Web.HttpContext)

Proporciona un identificador único para cada solicitud. Es muy útil para incluir en los registros.

string requestId = httpContext.TraceIdentifier;

HttpContext.Request

HttpContext.Request.HttpMethod se traduce en:

string httpMethod = httpContext.Request.Method;

HttpContext.Request.QueryString se traduce en:

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 y HttpContext.Request.RawUrl se traducen en:

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

HttpContext.Request.IsSecureConnection se traduce en:

var isSecureConnection = httpContext.Request.IsHttps;

HttpContext.Request.UserHostAddress se traduce en:

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

HttpContext.Request.Cookies se traduce en:

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 se traduce en:

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

HttpContext.Request.Headers se traduce en:

// 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 se traduce en:

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

HttpContext.Request.UrlReferrer se traduce en:

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

HttpContext.Request.ContentType se traduce en:

// 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 se traduce en:

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

Advertencia

Lea los valores de formulario solo si el subtipo de contenido es x-www-form-urlencoded o form-data.

HttpContext.Request.InputStream se traduce en:

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

Advertencia

Use este código solo en un middleware de tipo de controlador, al final de una canalización.

Puede leer el cuerpo sin procesar tal y como se muestra anteriormente solo una vez por solicitud. El middleware que intenta leer el cuerpo después de la primera lectura leerá un cuerpo vacío.

Esto no se aplica a la lectura de un formulario como se mostró anteriormente, porque esto se hace desde un búfer.

HttpContext.Response

HttpContext.Response.Status y HttpContext.Response.StatusDescription se traducen en:

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

HttpContext.Response.ContentEncoding y HttpContext.Response.ContentType se traducen en:

// 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 sí solo también se traduce en:

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

HttpContext.Response.Output se traduce en:

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

HttpContext.Response.TransmitFile

El servicio de un archivo se describe en Características de solicitud en ASP.NET Core.

HttpContext.Response.Headers

El envío de encabezados de respuesta es complicado por el hecho de que si los establece después de que se haya escrito algo en el cuerpo de la respuesta, no se enviarán.

La solución consiste en establecer un método de devolución de llamada al que se llamará justo antes de que se inicie la escritura en la respuesta. Esto es mejor hacerlo al principio del método Invoke en el middleware. Es este método de devolución de llamada el que establece los encabezados de respuesta.

El código siguiente establece un método de devolución de llamada denominado SetHeaders:

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

El método de devolución de llamada SetHeaders tendría el siguiente aspecto:

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

Cookie se desplaza al explorador en un encabezado de respuesta Set-Cookie. Como resultado, el envío de cookie requiere la misma devolución de llamada que se usó para enviar encabezados de respuesta:

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

El método de devolución de llamada SetCookies tendría el siguiente aspecto:

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 adicionales