Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo mostra como migrar módulos HTTP ASP.NET existentes do system.webserver para o middleware do ASP.NET Core.
Módulos revisitados
Antes de prosseguir para o middleware do ASP.NET Core, vamos primeiro recapitular como os módulos HTTP funcionam:
Os módulos são:
Classes que implementam IHttpModule
Invocado em cada solicitação
Capaz de fazer um curto-circuito (parar o processamento adicional de uma solicitação)
Capaz de adicionar à resposta HTTP ou criar suas próprias respostas
Configurado no Web.config
A ordem na qual os módulos processam solicitações de entrada é determinada por:
Uma série de eventos disparados por 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.
Para o mesmo evento, a ordem na qual eles estão configurados no Web.config.
Além dos módulos, você pode adicionar manipuladores para os eventos do ciclo de vida ao arquivo Global.asax.cs . Esses manipuladores são executados depois dos manipuladores nos módulos configurados.
De módulos a middleware
O middleware é mais simples que os módulos HTTP:
Módulos,
Global.asax.csWeb.config (exceto a configuração do IIS) e o ciclo de vida da aplicação foram removidos.As funções dos módulos foram assumidas pelo middleware
Os middlewares são configurados usando código ao invés de no Web.config
- A ramificação de pipeline permite enviar solicitações para middleware específico, com base não apenas na URL, mas também em cabeçalhos de solicitação, cadeias de caracteres de consulta etc.
- A ramificação de pipeline permite enviar solicitações para middleware específico, com base não apenas na URL, mas também em cabeçalhos de solicitação, cadeias de caracteres de consulta etc.
O middleware é muito semelhante aos módulos:
Invocado em geral para cada solicitação
Capaz de interromper uma solicitação, não passando a solicitação para o próximo middleware
Capaz de criar sua própria resposta HTTP
Middleware e módulos são processados em uma ordem diferente:
A ordem do middleware baseia-se na ordem em que eles são inseridos no pipeline de solicitação, enquanto a ordem dos módulos é baseada principalmente em System.Web.HttpApplication eventos.
A ordem do middleware para respostas é o inverso da de solicitações, enquanto a ordem dos módulos é a mesma para solicitações e respostas.
Consulte Criar um pipeline de middleware com IApplicationBuilder
Observe como, na imagem acima, o middleware de autenticação interrompeu a solicitação.
Migrando o código do módulo para o middleware
Um módulo HTTP existente será semelhante a este:
// 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 do ASP.NET Core é uma classe que expõe um Invoke método tomando um HttpContext e retornando um Task. Seu novo middleware terá esta 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 como escrever middleware.
A classe auxiliar MyMiddlewareExtensions facilita a configuração do middleware em sua Startup classe. O UseMyMiddleware método adiciona sua classe middleware ao pipeline de solicitação. Os serviços exigidos pelo middleware são injetados no construtor do middleware.
Seu módulo poderá 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 ao não chamar Invoke para o próximo middleware no pipeline. Tenha em mente que isso não encerra totalmente a solicitação, pois os softwares intermediários anteriores ainda serão invocados quando a resposta passar novamente 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 módulo para o novo middleware, você pode descobrir que o código não é compilado porque a HttpContext classe foi significativamente alterada no ASP.NET Core. Consulte Migrar de ASP.NET Framework HttpContext para ASP.NET Core para saber como migrar para o novo HttpContext do ASP.NET Core.
Migrando a inserção do módulo para o pipeline de solicitação
Os módulos HTTP normalmente 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>
Faça essa conversão adicionando seu novo middleware ao pipeline de solicitação na sua Startup classe:
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 em que você insere seu novo middleware depende do evento que ele manipula como um módulo (BeginRequest, EndRequest, etc.) e sua ordem na lista de módulos em Web.config.
Como indicado anteriormente, não há nenhum ciclo de vida do aplicativo no ASP.NET Core e a ordem na qual as respostas são processadas por middleware difere da ordem usada pelos módulos. Isso pode tornar 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.
Carregamento de opções de middleware usando o padrão de opções
Alguns módulos têm opções de configuração armazenadas em Web.config. No entanto, no ASP.NET Core, um novo modelo de configuração é usado no lugar de Web.config.
O novo sistema de configuração oferece estas opções para resolver isso:
Injete diretamente as opções no middleware, conforme mostrado na próxima seção.
Use o padrão de opções:
Crie uma classe para manter suas opções de middleware, por exemplo:
public class MyMiddlewareOptions { public string Param1 { get; set; } public string Param2 { get; set; } }Armazenar os valores de opção
O sistema de configuração permite que você armazene valores de opção em qualquer lugar desejado. No entanto, a maioria dos sites usa
appsettings.json, portanto, vamos adotar essa abordagem:{ "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }MyMiddlewareOptionsSection aqui é um nome de seção. Ele não precisa ser o mesmo que o nome da sua classe de opções.
Associar os valores de opção à classe de opções
O padrão de opções usa a estrutura de injeção de dependência do ASP.NET Core para associar o tipo de opções (como
MyMiddlewareOptions) a um objetoMyMiddlewareOptions, que tem as opções reais.Atualize sua
Startupclasse:Se você estiver usando
appsettings.json, adicione-o ao construtor de configuração noStartupconstrutor: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 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(); }Associe suas opções à 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(); }
Insira as opções no construtor de middleware. Isso é semelhante às opções de injeção 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ência.Isso não se limita a
IOptionsobjetos. Qualquer outro objeto necessário para seu middleware pode ser injetado dessa forma.
Carregamento de opções de middleware por injeção direta
O padrão de opções tem a vantagem de criar acoplamento flexível entre valores de opções e seus consumidores. Depois de associar uma classe de opções aos valores de opções reais, qualquer outra classe poderá obter acesso às opções por meio da estrutura de injeção de dependência. Não é necessário passar valores de opções.
Isso será interrompido se você quiser usar o mesmo middleware duas vezes, com opções diferentes. Por exemplo, um middleware de autorização usado em diferentes branches, permitindo funções diferentes. Você não pode associar dois objetos de opções diferentes à classe de opções um.
A solução é obter os objetos de opções com os valores de opções reais em sua Startup classe e passá-los diretamente para cada instância do seu middleware.
Adicionar uma segunda chave a
appsettings.jsonPara adicionar um segundo conjunto de opções ao
appsettings.jsonarquivo, use uma nova chave para identificá-la exclusivamente:{ "MyMiddlewareOptionsSection2": { "Param1": "Param1Value2", "Param2": "Param2Value2" }, "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }Recupere valores de 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 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?}"); }); }Habilite o middleware para usar um parâmetro de opções. Forneça uma sobrecarga para o método de extensão
Use...(que aceita o parâmetro de opções e o passa paraUseMiddleware). QuandoUseMiddlewareé chamado com parâmetros, ele passa os parâmetros para o construtor do middleware quando instancia o objeto 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
OptionsWrapperobjeto. Isso implementaIOptions, conforme esperado pelo construtor de middleware.
Migração incremental de IHttpModule
Há momentos em que a conversão de módulos em middleware não pode ser feita facilmente. Para dar suporte a cenários de migração nos quais os módulos são necessários e não podem ser movidos para o middleware, os adaptadores do System.Web dão suporte à adição deles ao ASP.NET Core.
Exemplo de IHttpModule
Para dar suporte a módulos, uma instância de HttpApplication deve estar disponível. Se nenhum HttpApplication personalizado for utilizado, um HttpApplication padrão será utilizado para adicionar os módulos. Os eventos declarados em um aplicativo personalizado (incluindo Application_Start) serão registrados e executados adequadamente.
using System.Web;
using Microsoft.AspNetCore.OutputCaching;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSystemWebAdapters()
.AddHttpApplication<MyApp>(options =>
{
// Size of pool for HttpApplication instances. Should be what the expected concurrent requests will be
options.PoolSize = 10;
// Register a module (optionally) by name
options.RegisterModule<MyModule>("MyModule");
});
// Only available in .NET 7+
builder.Services.AddOutputCache(options =>
{
options.AddHttpApplicationBasePolicy(_ => new[] { "browser" });
});
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthenticationEvents();
app.UseAuthorization();
app.UseAuthorizationEvents();
app.UseSystemWebAdapters();
app.UseOutputCache();
app.MapGet("/", () => "Hello World!")
.CacheOutput();
app.Run();
class MyApp : HttpApplication
{
protected void Application_Start()
{
}
public override string? GetVaryByCustomString(System.Web.HttpContext context, string custom)
{
// Any custom vary-by string needed
return base.GetVaryByCustomString(context, custom);
}
}
class MyModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += (s, e) =>
{
// Handle events at the beginning of a request
};
application.AuthorizeRequest += (s, e) =>
{
// Handle events that need to be authorized
};
}
public void Dispose()
{
}
}
Migração Global.asax
Essa infraestrutura pode ser usada para migrar o uso de Global.asax, se necessário. A origem Global.asax é uma HttpApplication personalizada e o arquivo pode ser incluído em um aplicativo ASP.NET Core. Como ele é nomeado Global, o código a seguir pode ser usado para registrá-lo:
builder.Services.AddSystemWebAdapters()
.AddHttpApplication<Global>();
Desde que a lógica dentro dela esteja disponível no ASP.NET Core, essa abordagem pode ser usada para migrar incrementalmente a dependência Global.asax para o ASP.NET Core.
Eventos de autenticação/autorização
Para que os eventos de autenticação e autorização sejam executados no momento desejado, o seguinte padrão deve ser usado:
app.UseAuthentication();
app.UseAuthenticationEvents();
app.UseAuthorization();
app.UseAuthorizationEvents();
Se isso não for feito, os eventos serão executados mesmo assim. No entanto, será durante a chamada de .UseSystemWebAdapters().
Pool de módulos HTTP
Como módulos e aplicativos no ASP.NET Framework foram atribuídos a uma solicitação, uma nova instância é necessária para cada solicitação. No entanto, como eles podem ser caros de criar, eles são agrupados usando ObjectPool<T>. Para personalizar o tempo de vida real das HttpApplication instâncias, um pool personalizado pode ser usado:
builder.Services.TryAddSingleton<ObjectPool<HttpApplication>>(sp =>
{
// Recommended to use the in-built policy as that will ensure everything is initialized correctly and is not intended to be replaced
var policy = sp.GetRequiredService<IPooledObjectPolicy<HttpApplication>>();
// Can use any provider needed
var provider = new DefaultObjectPoolProvider();
// Use the provider to create a custom pool that will then be used for the application.
return provider.Create(policy);
});