Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo mostra como migrar módulos HTTP ASP.NET existentes do system.webserver para o middleware ASP.NET Core.
Módulos revisitados
Antes de prosseguir para o middleware ASP.NET Core, vamos primeiro recapitular como os módulos HTTP funcionam:
Os módulos são:
Classes que implementam IHttpModule
Invocado para cada requisição
Capaz de curto-circuito (parar o processamento adicional de um pedido)
Capaz de adicionar à resposta HTTP ou criar a sua própria
Configurado em Web.config
A ordem na qual os módulos processam as 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 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 Global.asax.cs
arquivo. Esses manipuladores são executados após os manipuladores nos módulos configurados.
Dos módulos ao middleware
O middleware é mais simples do que os módulos HTTP:
Os módulos,
Global.asax.cs
, Web.config (exceto para a configuração do IIS) e o ciclo de vida da aplicação desapareceramAs funções dos módulos foram assumidas pelo middleware
O middleware é configurado usando código em vez de 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 princípio para cada pedido
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
O middleware e os módulos são processados em uma ordem diferente:
A ordem do middleware é baseada na ordem em que eles são inseridos no pipeline de solicitação, enquanto a ordem dos módulos é baseada principalmente em eventos System.Web.HttpApplication.
A ordem do middleware para respostas é o inverso da ordem das solicitações, enquanto a ordem dos módulos é a mesma para solicitações e respostas
Consulte Como criar um pipeline de middleware com IApplicationBuilder
Observe como na imagem acima, o middleware de autenticação curto-circuitou a solicitação.
Migrando o código do módulo para 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.
}
}
}
Como mostrado na página Middleware, um middleware do ASP.NET Core é uma classe que expõe um método Invoke
que aceita um HttpContext
e retorna 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 de middleware ao pipeline de solicitação. 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 pela não chamada do Invoke
no próximo middleware do pipeline. Lembre-se de que isto não encerra completamente a solicitação, uma vez que os middlewares anteriores ainda serão invocados 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 módulo para o novo middleware, você pode achar que seu código não é compilado porque a HttpContext
classe mudou significativamente no ASP.NET Core. Consulte Migrar do ASP.NET Framework HttpContext para o ASP.NET Core para saber como migrar para o novo ASP.NET Core HttpContext.
Migrando a inserção do módulo no 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>
Converta isso adicionando seu novo middleware ao pipeline de solicitações em 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 local exato no pipeline onde você insere seu novo middleware depende do evento que ele manipulou como um módulo (BeginRequest
, EndRequest
, etc.) e sua ordem na sua lista de módulos em 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 sua decisão de pedido mais desafiadora.
Se o pedido se tornar um problema, você pode dividir seu módulo em vários componentes de middleware que podem ser encomendados independentemente.
Carregando opções de middleware usando o padrão de opções
Alguns módulos têm opções de configuração que sã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 armazenar 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 armazenar valores de opção onde quiser. No entanto, a maioria dos sites usa
appsettings.json
, então adotaremos essa abordagem.{ "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }
MyMiddlewareOptionsSection aqui é um nome de seção. 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 umMyMiddlewareOptions
objeto que tenha as opções reais.Atualize a sua
Startup
classe:Se estiver a utilizar
appsettings.json
, adicione-o ao construtor de configuração noStartup
construtor.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 à 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(); }
Injete as opções em seu construtor de middleware. Isto é 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 o seu middleware ao
IApplicationBuilder
cuida da injeção de dependências.Isso não se limita a
IOptions
objetos. Qualquer outro objeto que seu middleware exija 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 um acoplamento frouxo entre os valores das opções e seus consumidores. Depois de associar uma classe options aos valores de opções reais, qualquer outra classe pode obter acesso às opções por meio da estrutura de injeção de dependência. Não há necessidade de repassar valores de opções.
Isso quebra se você quiser usar o mesmo middleware duas vezes, com opções diferentes. Por exemplo, um middleware de autorização usado em diferentes ramificações permitindo diferentes funções. Não é possível associar dois objetos de opções diferentes à classe de opções única.
A solução é obter os objetos de opções com os valores reais das opções na sua classe Startup
e passá-los diretamente para cada instância do middleware.
Adicione uma segunda chave a
appsettings.json
Para adicionar um segundo conjunto de opções ao
appsettings.json
arquivo, use uma nova chave para identificá-lo exclusivamente:{ "MyMiddlewareOptionsSection2": { "Param1": "Param1Value2", "Param2": "Param2Value2" }, "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }
Recupere valores de opções e passe-os para o middleware. O
Use...
método de extensão (que adiciona seu middleware ao pipeline) é um local lógico para passar os valores de opção: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?}"); }); }
Permitir que o middleware utilize um parâmetro options. Forneça uma sobrecarga do método de extensão
Use...
(que recebe o parâmetro options e o passa paraUseMiddleware
). QuandoUseMiddleware
é chamado com parâmetros, ele passa os parâmetros para o construtor de middleware quando ele instancia 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 options em um
OptionsWrapper
objeto. 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 middleware, os adaptadores System.Web suportam adicioná-los ao ASP.NET Core.
Exemplo de IHttpModule
Para suportar módulos, uma instância de HttpApplication deve estar disponível. Se não for usado um HttpApplication personalizado, será utilizado um padrão para adicionar os módulos a ele. Os eventos declarados em um aplicativo personalizado (incluindo Application_Start
) serão registrados e executados de acordo.
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
Esta infraestrutura pode ser usada para migrar a utilização de Global.asax
se necessário. A fonte de Global.asax
é um HttpApplication personalizado e o ficheiro pode ser incluído numa aplicação ASP.NET Core. Uma vez que é nomeado Global
, o seguinte código 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, esta abordagem pode ser usada para migrar incrementalmente a dependência de 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 continuarão a ser executados. No entanto, será durante a chamada de .UseSystemWebAdapters()
.
Pool de módulos HTTP
Como os 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 para 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);
});