Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article explique comment migrer des modules HTTP ASP.NET existants de system.webserver vers ASP.NET intergiciel Core.
Modules revisités
Avant de passer à ASP.NET intergiciel Core, récapitule tout d’abord le fonctionnement des modules HTTP :
Les modules sont les suivants :
Classes qui implémentent IHttpModule
Appelé pour chaque requête
Possibilité de court-circuit (arrêter le traitement ultérieur d’une demande)
Possibilité d’ajouter à la réponse HTTP ou de créer sa propre réponse
Configuré dans Web.config
L’ordre dans lequel les modules traitent les demandes entrantes est déterminé par :
Événements de série déclenchés par ASP.NET, tels que BeginRequest et AuthenticateRequest. Pour obtenir une liste complète, consultez System.Web.HttpApplication. Chaque module peut créer un gestionnaire pour un ou plusieurs événements.
Pour le même événement, l’ordre dans lequel ils sont configurés dans Web.config.
En plus des modules, vous pouvez ajouter des gestionnaires pour les événements de cycle de vie à votre Global.asax.cs fichier. Ces gestionnaires s’exécutent après les gestionnaires des modules configurés.
Des modules au middleware
Les intergiciels sont plus simples que les modules HTTP :
Modules,
Global.asax.csWeb.config (à l’exception de la configuration IIS) et le cycle de vie de l’application sont disparusLes rôles des modules ont été repris par l’intergiciel
Les intergiciels sont configurés à l’aide du code plutôt que dans Web.config
- Le branchement de pipeline vous permet d’envoyer des requêtes à un intergiciel spécifique, en fonction non seulement de l’URL, mais également des en-têtes de requête, des chaînes de requête, etc.
- Le branchement de pipeline vous permet d’envoyer des requêtes à un intergiciel spécifique, en fonction non seulement de l’URL, mais également des en-têtes de requête, des chaînes de requête, etc.
Les intergiciels sont très similaires aux modules :
Appelé en principe pour chaque requête
Capable de court-circuiter une requête, en ne transmettant pas la requête au middleware suivant
Possibilité de créer sa propre réponse HTTP
Les intergiciels et les modules sont traités dans un ordre différent :
L’ordre des intergiciels est basé sur l’ordre dans lequel ils sont insérés dans le pipeline de requête, tandis que l’ordre des modules est principalement basé sur System.Web.HttpApplication les événements.
L’ordre des intergiciels pour les réponses est l’inverse de celui des demandes, tandis que l’ordre des modules est le même pour les demandes et les réponses
Voir Créer un pipeline d’intergiciels avec IApplicationBuilder
Notez comment, dans l’image ci-dessus, l’intergiciel d’authentification a court-circuité la requête.
Migration du code de module vers un intergiciel
Un module HTTP existant ressemble à ceci :
// 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.
}
}
}
Comme indiqué dans la page Middleware, un intergiciel ASP.NET Core est une classe qui expose une Invoke méthode prenant un HttpContext et retournant un Task. Votre nouvel intergiciel ressemble à ceci :
// 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>();
}
}
}
Le modèle de middleware précédent a été extrait de la section sur l'écriture de middleware.
La classe d’assistance MyMiddlewareExtensions facilite la configuration de votre intergiciel dans votre Startup classe. La UseMyMiddleware méthode ajoute votre classe middleware au pipeline de requête. Les services requis par l’intergiciel sont injectés dans le constructeur du middleware.
Votre module peut mettre fin à une demande, par exemple si l’utilisateur n’est pas autorisé :
// 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 intergiciel gère cela en n’appelant Invoke pas le middleware suivant dans le pipeline. N’oubliez pas que cela n’arrête pas complètement la requête, car les intergiciels précédents seront toujours appelés lorsque la réponse revient dans le 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.
}
Lorsque vous migrez les fonctionnalités de votre module vers votre nouveau middleware, vous pouvez constater que votre code ne se compile pas, car la HttpContext classe a considérablement changé dans ASP.NET Core. Consultez Migrer de ASP.NET Framework HttpContext vers ASP.NET Core pour découvrir comment migrer vers la nouvelle ASP.NET Core HttpContext.
Migration de l’insertion de modules dans le pipeline des requêtes
Les modules HTTP sont généralement ajoutés au pipeline de requête à l’aide deWeb.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>
Convertissez-le en ajoutant votre nouveau middleware au pipeline de requête dans votre 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?}");
});
}
L’emplacement exact dans le pipeline où vous insérez votre nouvel intergiciel dépend de l’événement qu’il a géré en tant que module (BeginRequest, EndRequestetc.) et de son ordre dans votre liste de modules dans Web.config.
Comme indiqué précédemment, il n’existe aucun cycle de vie d’application dans ASP.NET Core et l’ordre dans lequel les réponses sont traitées par middleware diffère de l’ordre utilisé par les modules. Cela pourrait rendre votre décision de commande plus difficile.
Si l’ordre devient un problème, vous pouvez fractionner votre module en plusieurs composants middleware qui peuvent être ordonnés indépendamment.
Chargement des options d’intergiciel à l’aide du modèle d’options
Certains modules ont des options de configuration stockées dans Web.config. Toutefois, dans ASP.NET Core, un nouveau modèle de configuration est utilisé à la place de Web.config.
Le nouveau système de configuration vous offre ces options pour résoudre ce problème :
Injectez directement les options dans l’intergiciel, comme indiqué dans la section suivante.
Utilisez le modèle d’options :
Créez une classe pour contenir vos options d’intergiciel, par exemple :
public class MyMiddlewareOptions { public string Param1 { get; set; } public string Param2 { get; set; } }Stocker les valeurs d’option
Le système de configuration vous permet de stocker les valeurs d’option n’importe où vous le souhaitez. Toutefois, la plupart des sites utilisent
appsettings.json, nous allons donc adopter cette approche :{ "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }MyMiddlewareOptionsSection voici un nom de section. Il n’est pas obligé d’être identique au nom de votre classe d’options.
Associer les valeurs d’option à la classe options
Le modèle d’options utilise le framework d’injection de dépendances d’ASP.NET Core pour associer le type d’options (par exemple
MyMiddlewareOptions) à un objetMyMiddlewareOptionsqui contient les options réelles.Mettez à jour votre
Startupclasse :Si vous utilisez
appsettings.json, ajoutez-le au générateur de configuration dans leStartupconstructeur :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(); }Configurez le service options :
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(); }Associez vos options à votre classe d’options :
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(); }
Injectez les options dans votre constructeur d’intergiciel. Cela est similaire à l’injection d’options dans un contrôleur.
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 } }La méthode d’extension UseMiddleware, qui ajoute votre intergiciel, prend en charge l’injection de dépendances pour le
IApplicationBuilder.Cela n’est pas limité aux
IOptionsobjets. Tout autre objet requis par votre intergiciel peut être injecté de cette façon.
Chargement des options d’intergiciel via l’injection directe
Le modèle d’options présente l’avantage qu’il crée un couplage libre entre les valeurs d’options et leurs consommateurs. Une fois que vous avez associé une classe d’options aux valeurs d’options réelles, toute autre classe peut accéder aux options via l’infrastructure d’injection de dépendances. Il n’est pas nécessaire de propager les valeurs des options.
Cela se décompose toutefois si vous souhaitez utiliser le même intergiciel deux fois, avec différentes options. Par exemple, un intergiciel d’autorisation utilisé dans différentes branches autorisant différents rôles. Vous ne pouvez pas associer deux objets d’options différents à la classe d’options unique.
La solution consiste à obtenir les objets d’options avec les valeurs d’options réelles de votre Startup classe et à les transmettre directement à chaque instance de votre intergiciel.
Ajouter une deuxième clé à
appsettings.jsonPour ajouter un deuxième ensemble d’options au
appsettings.jsonfichier, utilisez une nouvelle clé pour l’identifier de manière unique :{ "MyMiddlewareOptionsSection2": { "Param1": "Param1Value2", "Param2": "Param2Value2" }, "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }Récupérez les valeurs des options et passez-les au middleware. La méthode d’extension
Use...(qui ajoute votre middleware au pipeline) est un endroit logique pour passer les valeurs d’option :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?}"); }); }Activez le middleware pour accepter un paramètre options. Fournissez une surcharge de la méthode d’extension
Use...(qui prend le paramètre d’options et le transmet àUseMiddleware). Lorsqu’ilUseMiddlewareest appelé avec des paramètres, il transmet les paramètres à votre constructeur d’intergiciel lorsqu’il instancie l’objet 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)); } }Notez comment cela encapsule l’objet options dans un
OptionsWrapperobjet. Cela implémenteIOptions, comme prévu par le constructeur d’intergiciel.
Migration incrémentielle IHttpModule
Il y a des moments où il n'est pas facile de convertir des modules en intergiciels. Pour prendre en charge les scénarios de migration dans lesquels les modules sont requis et ne peuvent pas être déplacés vers l’intergiciel, les adaptateurs System.Web prennent en charge leur ajout à ASP.NET Core.
Exemple d'IHttpModule
Pour prendre en charge les modules, une instance de HttpApplication doit être disponible. Si aucune personnalisée HttpApplication n’est utilisée, une valeur par défaut est utilisée pour ajouter les modules. Les événements déclarés dans une application personnalisée (y compris Application_Start) sont enregistrés et exécutés en conséquence.
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()
{
}
}
Migration de Global.asax
Cette infrastructure peut être utilisée pour migrer l’utilisation du Global.asax cas échéant. La source depuis Global.asax est un HttpApplication personnalisé, et le fichier peut être inclus dans une application ASP.NET Core. Étant donné qu’il est nommé Global, le code suivant peut être utilisé pour l’inscrire :
builder.Services.AddSystemWebAdapters()
.AddHttpApplication<Global>();
Tant que la logique qu’elle contient est disponible dans ASP.NET Core, cette approche peut être utilisée pour migrer progressivement la dépendance à Global.asax vers ASP.NET Core.
Événements d’authentification/autorisation
Pour que les événements d’authentification et d’autorisation s’exécutent au moment souhaité, le modèle suivant doit être utilisé :
app.UseAuthentication();
app.UseAuthenticationEvents();
app.UseAuthorization();
app.UseAuthorizationEvents();
Si ce n’est pas le cas, les événements continueront à s'exécuter. Toutefois, cela se produira au cours de l'appel de .UseSystemWebAdapters().
Regroupement de modules HTTP
Étant donné que les modules et les applications dans ASP.NET Framework ont été affectés à une demande, une nouvelle instance est nécessaire pour chaque requête. Toutefois, étant donné qu’ils peuvent être coûteux à créer, ils sont mis en commun à l’aide ObjectPool<T>. Pour personnaliser la durée de vie réelle des HttpApplication instances, un pool personnalisé peut être utilisé :
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);
});