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:
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:
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.
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 desaparecidoEl 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:
Se invoca en principio para cada solicitud
Capaz de producir un cortocircuito en una solicitud sin pasar la solicitud al siguiente middleware
Capaz de crear su propia respuesta HTTP
El middleware y los módulos se procesan en un orden diferente:
El orden del middleware se basa en el orden en que se insertan en la canalización de solicitudes, mientras que el orden de los módulos se basa principalmente en los eventos System.Web.HttpApplication.
El orden del middleware para las respuestas es el inverso del de las solicitudes, mientras que el orden de los módulos es el mismo para las solicitudes y respuestas
Consulte Creación de una canalización de middleware con IApplicationBuilder
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:
Una expresión lambda que toma
HttpContext
y devuelvetrue
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.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:
Inserte directamente las opciones en el middleware, como se muestra en la sección siguiente.
Use el patrón de opciones:
Cree una clase para contener las opciones de middleware, por ejemplo:
public class MyMiddlewareOptions { public string Param1 { get; set; } public string Param2 { get; set; } }
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.
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 objetoMyMiddlewareOptions
que tiene las opciones reales.Actualice la clase
Startup
:Si usa
appsettings.json
, agréguela al generador de configuración en el constructorStartup
: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(); }
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(); }
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(); }
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.
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" } }
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?}"); }); }
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 aUseMiddleware
). Cuando se llama aUseMiddleware
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 implementaIOptions
, 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);
}