Delen via


HTTP-modules migreren naar ASP.NET Core-middleware

In dit artikel wordt beschreven hoe u bestaande ASP.NET HTTP-modules migreert van system.webserver naar ASP.NET Core middleware.

Opnieuw bekeken modules

Voordat u doorgaat met ASP.NET Core middleware, laten we eerst een samenvatting maken van de werking van HTTP-modules:

Modules-handler

Modules zijn:

  • Klassen die implementeren IHttpModule

  • Aangeroepen voor elke aanvraag

  • Kan kortsluiten (verdere verwerking van een aanvraag stoppen)

  • Kan toevoegen aan het HTTP-antwoord of een eigen HTTP-antwoord creëren

  • Geconfigureerd in Web.config

De volgorde waarin modules binnenkomende aanvragen verwerken, wordt bepaald door:

  1. Een reeks gebeurtenissen die door ASP.NET worden geactiveerd, zoals BeginRequest en AuthenticateRequest. Zie voor een volledige lijst System.Web.HttpApplication. Elke module kan een handler maken voor een of meer gebeurtenissen.

  2. Voor dezelfde gebeurtenis is de volgorde waarin ze zijn geconfigureerd in Web.config.

Naast modules kunt u handlers voor de levenscyclus-gebeurtenissen toevoegen aan uw Global.asax.cs bestand. Deze handlers worden uitgevoerd na de handlers in de geconfigureerde modules.

Van modules tot middleware

Middleware zijn eenvoudiger dan HTTP-modules:

  • Modules, Global.asax.csWeb.config (met uitzondering van IIS-configuratie) en de levenscyclus van de toepassing zijn verdwenen

  • De rollen van modules zijn overgenomen door middleware

  • Middleware wordt geconfigureerd met behulp van code in plaats van in Web.config

  • Met pijplijnvertakking kunt u aanvragen verzenden naar specifieke middleware, op basis van niet alleen de URL, maar ook op aanvraagheaders, queryreeksen, enzovoort.
  • Met pijplijnvertakking kunt u aanvragen verzenden naar specifieke middleware, op basis van niet alleen de URL, maar ook op aanvraagheaders, queryreeksen, enzovoort.

Middleware is vergelijkbaar met modules:

Middleware en modules worden in een andere volgorde verwerkt:

  • De volgorde van middleware is gebaseerd op de volgorde waarin ze worden ingevoegd in de aanvraagpijplijn, terwijl de volgorde van modules voornamelijk is gebaseerd op System.Web.HttpApplication gebeurtenissen.

  • De volgorde van middleware voor antwoorden is het omgekeerde van die voor aanvragen, terwijl de volgorde van modules hetzelfde is voor aanvragen en antwoorden

  • Zie Een middleware-pijplijn maken met IApplicationBuilder

Authorization Middleware onderbreekt een verzoek voor een gebruiker die niet geautoriseerd is. Een aanvraag voor de indexpagina is toegestaan en wordt verwerkt door MVC Middleware. Een aanvraag voor een verkooprapport is toegestaan en wordt verwerkt door een aangepaste rapportmiddleware.

Let op dat in de bovenstaande afbeelding de verificatie-middleware de aanvraag vroegtijdig heeft afgebroken.

Modulecode migreren naar middleware

Een bestaande HTTP-module ziet er ongeveer als volgt uit:

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

Zoals zichtbaar op de Middleware pagina, is een ASP.NET Core middleware een klasse die een methode bevat die een HttpContext als parameter neemt en een Task retourneert. Uw nieuwe middleware ziet er als volgt uit:

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

De voorgaande middlewaresjabloon is afkomstig uit de sectie over het schrijven van middleware.

De helperklasse MyMiddlewareExtensions maakt het eenvoudiger om uw middleware in uw Startup klas te configureren. Met de UseMyMiddleware methode wordt uw middlewareklasse toegevoegd aan de aanvraagpijplijn. Services die door de middleware zijn vereist, worden geïnjecteerd in de constructor van de middleware.

Uw module kan een aanvraag beëindigen, bijvoorbeeld als de gebruiker niet is geautoriseerd:

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

Een middleware verwerkt dit door Invoke niet op de volgende middleware in de pijplijn aan te roepen. Houd er rekening mee dat dit de aanvraag niet volledig beëindigt, omdat eerdere middlewares nog steeds worden aangeroepen wanneer het antwoord terugkomt via de pijplijn.

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

Wanneer u de functionaliteit van uw module naar uw nieuwe middleware migreert, kan het zijn dat uw code niet compileert omdat de HttpContext klasse aanzienlijk is gewijzigd in ASP.NET Core. Zie Migreren van ASP.NET Framework HttpContext naar ASP.NET Core voor meer informatie over het migreren naar de nieuwe ASP.NET Core HttpContext.

Migreren van module-invullen in de verzoekpipet

HTTP-modules worden doorgaans toegevoegd aan de aanvraagpijplijn met 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>

Converteer dit door uw nieuwe middleware toe te voegen aan de aanvraagpijplijn in uw Startup klasse:

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?}");
    });
}

De exacte plek in de pijplijn waar u de nieuwe middleware invoegt, is afhankelijk van de gebeurtenis die wordt verwerkt als een module (BeginRequest, EndRequestenzovoort) en de volgorde ervan in uw lijst met modules in Web.config.

Zoals eerder vermeld, is er geen levenscyclus van toepassingen in ASP.NET Core en de volgorde waarin reacties worden verwerkt door middleware verschilt van de volgorde die door modules wordt gebruikt. Dit kan ervoor zorgen dat uw bestellingsbeslissing lastiger wordt.

Als bestellen een probleem wordt, kunt u uw module splitsen in meerdere middlewareonderdelen die onafhankelijk van elkaar kunnen worden geordend.

Middlewareopties laden met behulp van het optiespatroon

Sommige modules hebben configuratieopties die zijn opgeslagen in Web.config. In ASP.NET Core wordt echter een nieuw configuratiemodel gebruikt in plaats van Web.config.

Het nieuwe configuratiesysteem biedt u deze opties om dit op te lossen:

  1. Maak een klasse voor het opslaan van uw middlewareopties, bijvoorbeeld:

    public class MyMiddlewareOptions
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
    }
    
  2. De optiewaarden opslaan

    Met het configuratiesysteem kunt u optiewaarden opslaan waar u maar wilt. De meeste sites gebruiken echter appsettings.json, dus we nemen die benadering:

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

    MyMiddlewareOptionsSection is hier een sectienaam. Het hoeft niet hetzelfde te zijn als de naam van uw optiesklasse.

  3. De optiewaarden koppelen aan de klasse Opties

    Het optiespatroon maakt gebruik van het afhankelijkheidsinjectieframework van ASP.NET Core om het optiestype (zoals MyMiddlewareOptions) te koppelen aan een MyMiddlewareOptions object met de werkelijke opties.

    Werk uw Startup klas bij:

    1. Als u appsettings.json gebruikt, voeg het dan toe aan de configuratiebouwer in de Startup constructor.

      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. Configureer de optiedienst.

      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. Koppel uw opties aan uw optiesklasse:

      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. Injecteer de opties in de middleware constructor. Dit is vergelijkbaar met het injecteren van opties in een controller.

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

    De UseMiddleware-extensiemethode die uw middleware toevoegt aan de IApplicationBuilder zorgt voor de afhandeling van afhankelijkheidsinjectie.

    Dit is niet beperkt tot IOptions objecten. Elk ander object dat uw middleware nodig heeft, kan op deze manier worden geïnjecteerd.

Opties voor middleware laden via directe injectie

Het optiespatroon heeft het voordeel dat het losse koppeling tussen optieswaarden en hun consumenten maakt. Nadat u een optiesklasse hebt gekoppeld aan de werkelijke waarden voor opties, kan elke andere klasse toegang krijgen tot de opties via het afhankelijkheidsinjectieframework. U hoeft geen opties door te geven.

Dit wordt echter opgesplitst als u dezelfde middleware twee keer wilt gebruiken, met verschillende opties. Bijvoorbeeld een autorisatie-middleware die in verschillende vertakkingen wordt gebruikt, waardoor verschillende rollen worden toegestaan. U kunt geen twee verschillende optiesobjecten koppelen aan de ene optiesklasse.

De oplossing is om de optiesobjecten op te halen met de werkelijke waarden voor opties in uw Startup klasse en deze rechtstreeks door te geven aan elk exemplaar van uw middleware.

  1. Een tweede sleutel toevoegen aan appsettings.json

    Als u een tweede set opties aan het appsettings.json bestand wilt toevoegen, gebruikt u een nieuwe sleutel om deze uniek te identificeren:

    {
      "MyMiddlewareOptionsSection2": {
        "Param1": "Param1Value2",
        "Param2": "Param2Value2"
      },
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    
  2. Optieswaarden ophalen en doorgeven aan middleware. De Use... extensiemethode (waarmee uw middleware aan de pijplijn wordt toegevoegd) is een logische plaats om de optiewaarden door te geven:

    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. Schakel middleware in om een parameter voor opties te gebruiken. Geef een overbelasting van de Use... extensiemethode op (die de parameter opties gebruikt en doorgeeft aan UseMiddleware). Wanneer UseMiddleware wordt aangeroepen met parameters, worden de parameters doorgegeven aan de middlewareconstructor wanneer het middleware-object wordt geïnstitueerd.

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

    Let op hoe dit het optiesobject in een OptionsWrapper object verpakt. Dit implementeert IOptions, zoals verwacht door de middlewareconstructor.

Incrementele IHttpModule-migratie

Er zijn momenten waarop het converteren van modules naar middleware niet eenvoudig kan worden uitgevoerd. Om migratiescenario's te ondersteunen waarin modules vereist zijn en niet naar middleware kunnen worden verplaatst, ondersteunen System.Web-adapters het toevoegen van modules aan ASP.NET Core.

IHttpModule-voorbeeld

Als u modules wilt ondersteunen, moet er een exemplaar van HttpApplication beschikbaar zijn. Als er geen aangepaste HttpApplication code wordt gebruikt, wordt er een standaard gebruikt om de modules toe te voegen. Gebeurtenissen die zijn gedeclareerd in een aangepaste toepassing (inclusief Application_Start) worden geregistreerd en dienovereenkomstig uitgevoerd.

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()
    {
    }
}

Global.asax-migratie

Deze infrastructuur kan worden gebruikt voor het migreren van het gebruik van Global.asax indien nodig. De bron van Global.asax is een aangepaste HttpApplication en het bestand kan worden opgenomen in een ASP.NET Core-applicatie. Omdat deze de naam Globalheeft, kan de volgende code worden gebruikt om deze te registreren:

builder.Services.AddSystemWebAdapters()
    .AddHttpApplication<Global>();

Zolang de logica in ASP.NET Core beschikbaar is, kan deze benadering worden gebruikt om geleidelijk de afhankelijkheid van Global.asax over te schakelen naar ASP.NET Core.

Verificatie-/autorisatie-gebeurtenissen

Als u wilt dat de verificatie- en autorisatie-gebeurtenissen op het gewenste tijdstip worden uitgevoerd, moet het volgende patroon worden gebruikt:

app.UseAuthentication();
app.UseAuthenticationEvents();

app.UseAuthorization();
app.UseAuthorizationEvents();

Als dit niet gedaan is, blijven de gebeurtenissen plaatsvinden. Het zal echter tijdens het aanroepen van .UseSystemWebAdapters().

Pooling van de HTTP-module

Omdat modules en toepassingen in ASP.NET Framework zijn toegewezen aan een aanvraag, is er een nieuw exemplaar nodig voor elke aanvraag. Omdat ze echter duur kunnen zijn om te maken, worden ze gegroepeerd met behulp van ObjectPool<T>. Als u de werkelijke levensduur van de HttpApplication exemplaren wilt aanpassen, kan een aangepaste pool worden gebruikt:

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

Aanvullende bronnen