Dela via


Migrera HTTP-moduler till ASP.NET Core-mellanprogram

Den här artikeln visar hur du migrerar befintliga ASP.NET HTTP-moduler från system.webserver till ASP.NET Core-mellanprogram.

Modulerna har återbesökts

Innan vi fortsätter till ASP.NET Core-mellanprogram ska vi först sammanfatta hur HTTP-moduler fungerar:

Modulhanterare

Moduler är:

  • Klasser som implementerar IHttpModule

  • Anropas för varje begäran

  • Det går att avbryta vidare bearbetning av en begäran.

  • Kan lägga till i HTTP-svaret eller skapa egna

  • Konfigurerad i Web.config

I vilken ordning moduler bearbetar inkommande begäranden bestäms av:

  1. En seriehändelser som utlöses av ASP.NET, till exempel BeginRequest och AuthenticateRequest. En fullständig lista finns i System.Web.HttpApplication. Varje modul kan skapa en hanterare för en eller flera händelser.

  2. För samma händelse är den ordning i vilken de konfigureras i Web.config.

Förutom moduler kan du lägga till hanterare för livscykelhändelserna i filen Global.asax.cs . Dessa hanterare körs efter hanterarna i de konfigurerade modulerna.

Från moduler till mellanprogram

Mellanprogram är enklare än HTTP-moduler:

  • Moduler, Global.asax.cs, Web.config (förutom IIS-konfiguration) och programmets livscykel är borta

  • Rollerna för moduler har tagits över av mellanprogram

  • Mellanprogram konfigureras med hjälp av kod snarare än i Web.config

  • Med pipelineförgrening kan du skicka begäranden till specifika mellanprogram, baserat på inte bara URL:en utan även på begärandehuvuden, frågesträngar osv.
  • Med pipelineförgrening kan du skicka begäranden till specifika mellanprogram, baserat på inte bara URL:en utan även på begärandehuvuden, frågesträngar osv.

Mellanprogram är mycket lika moduler:

Mellanprogram och moduler bearbetas i en annan ordning:

Authorization Middleware avbryter en begäran för en användare som inte har behörighet. En begäran för sidan Index tillåts och bearbetas av MVC Middleware. En begäran efter en försäljningsrapport tillåts och bearbetas av ett anpassat rapportmellanprogram.

Observera hur i bilden ovan kortsluter autentiseringsmellanprogrammet begäran.

Migrera modulkod till mellanprogram

En befintlig HTTP-modul ser ut ungefär så här:

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

Som du ser på sidan Mellanprogram är ett ASP.NET Core-mellanprogram en klass som exponerar en Invoke metod som tar en HttpContext och returnerar en Task. Ditt nya mellanprogram ser ut så här:

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

Föregående mall för mellanprogram togs från avsnittet om att skriva mellanprogram.

Hjälpklassen MyMiddlewareExtensions gör det enklare att konfigurera mellanprogrammet i klassenStartup. Metoden UseMyMiddleware lägger till din mellanprogramsklass i begärandepipelinen. Tjänster som krävs av mellanprogrammet matas in i mellanprogrammets konstruktor.

Modulen kan avsluta en begäran, till exempel om användaren inte har behörighet:

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

Ett mellanprogram hanterar detta genom att inte anropa Invoke nästa mellanprogram i pipelinen. Tänk på att detta inte helt avslutar begäran, eftersom tidigare mellanprogram fortfarande anropas när svaret tar sig tillbaka genom pipelinen.

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

När du migrerar modulens funktioner till ditt nya mellanprogram kan det hända att koden inte kompileras eftersom HttpContext klassen har ändrats avsevärt i ASP.NET Core. Se Migrera från ASP.NET Framework HttpContext till ASP.NET Core för att lära dig hur du migrerar till den nya ASP.NET Core HttpContext.

Migrera modulinfogning till pipelinen för begäran

HTTP-moduler läggs vanligtvis till i begärans pipeline med 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>

Konvertera detta genom att lägga till ditt nya mellanprogram i pipelinen för begäran i klassen 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?}");
    });
}

Den exakta platsen i pipelinen där du infogar ditt nya mellanprogram beror på vilken händelse det hanterade som en modul (BeginRequest, EndRequestosv.) och dess ordning i listan över moduler i Web.config.

Som tidigare nämnts finns det ingen programlivscykel i ASP.NET Core och i vilken ordning svar bearbetas av mellanprogram skiljer sig från den ordning som används av moduler. Detta kan göra beställningsbeslutet mer utmanande.

Om beställningen blir ett problem kan du dela upp modulen i flera mellanprogramskomponenter som kan beställas separat.

Ladda middleware-alternativ med hjälp av alternativmönstret

Vissa moduler har konfigurationsalternativ som lagras i Web.config. I ASP.NET Core används dock en ny konfigurationsmodell i stället förWeb.config.

Det nya konfigurationssystemet ger dig följande alternativ för att lösa detta:

  1. Skapa en klass som innehåller mellanprogramsalternativen, till exempel:

    public class MyMiddlewareOptions
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
    }
    
  2. Lagra alternativvärdena

    Med konfigurationssystemet kan du lagra alternativvärden var du vill. De flesta webbplatser använder appsettings.jsondock , så vi använder den metoden:

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

    MyMiddlewareOptionsSection här är ett avsnittsnamn. Det behöver inte vara samma som namnet på din alternativklass.

  3. Associera alternativvärdena med alternativklassen

    Alternativmönstret använder ASP.NET Cores beroendeinmatningsramverk för att associera alternativtypen (till exempel MyMiddlewareOptions) med ett MyMiddlewareOptions objekt som har de faktiska alternativen.

    Uppdatera klassen Startup :

    1. Om du använder appsettings.json, lägg till det i konfigurationsbyggaren i konstruktorn Startup.

      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. Konfigurera alternativtjänsten:

      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. Associera dina alternativ med din alternativklass:

      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. Mata in alternativen i mellanprogramskonstruktorn. Detta liknar inmatningsalternativ i en styrenhet.

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

    UseMiddleware-tilläggsmetoden som lägger till ditt mellanprogram i IApplicationBuilder tar hand om beroendeinjektionen.

    Detta är inte begränsat till IOptions objekt. Alla andra objekt som ditt mellanprogram kräver kan matas in på det här sättet.

Läsa in mellanprogramsalternativ via direktinmatning

Alternativmönstret har fördelen att det skapar lös koppling mellan alternativvärden och deras konsumenter. När du har associerat en alternativklass med de faktiska alternativvärdena kan alla andra klasser få åtkomst till alternativen via beroendeinmatningsramverket. Du behöver inte skicka runt alternativvärden.

Detta delas dock upp om du vill använda samma mellanprogram två gånger, med olika alternativ. Till exempel ett mellanprogram för auktorisering som används i olika grenar som tillåter olika roller. Du kan inte associera två olika alternativobjekt med klassen one options.

Lösningen är att hämta alternativobjekten med de faktiska alternativvärdena i klassen Startup och skicka dem direkt till varje instans av mellanprogrammet.

  1. Lägg till en andra nyckel i appsettings.json

    Om du vill lägga till en andra uppsättning alternativ i appsettings.json filen använder du en ny nyckel för att unikt identifiera den:

    {
      "MyMiddlewareOptionsSection2": {
        "Param1": "Param1Value2",
        "Param2": "Param2Value2"
      },
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    
  2. Hämta alternativvärden och skicka dem till mellanprogram. Tilläggsmetoden Use... (som lägger till mellanprogrammet i pipelinen) är en logisk plats för att skicka in alternativvärdena:

    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. Aktivera mellanprogram för att ta en alternativparameter. Tillhandahåll en överlagring av Use...-tilläggsmetoden (som tar alternativparametern och skickar den till UseMiddleware). När UseMiddleware anropas med parametrar skickas parametrarna till mellanprogramskonstruktorn när det instansierar mellanprogramsobjektet.

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

    Observera hur detta omsluter alternativobjektet i ett OptionsWrapper objekt. Detta implementerar IOptions, som förväntat av mellanprogramskonstruktorn.

Inkrementell IHttpModule-migrering

Det finns tillfällen då det inte är enkelt att konvertera moduler till mellanprogram. För att stödja migreringsscenarier i vilka moduler krävs och inte kan flyttas till mellanlager, har System.Web-adaptrar stöd för att lägga till dem i ASP.NET Core.

IHttpModule-exempel

För att kunna stödja moduler måste en instans av HttpApplication vara tillgänglig. Om ingen anpassad HttpApplication används kommer en standardinställning att användas för att lägga till modulerna i. Händelser som deklareras i ett anpassat program (inklusive Application_Start) registreras och körs därefter.

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

Den här infrastrukturen kan användas för att migrera användningen av Global.asax vid behov. Källan från Global.asax är anpassad HttpApplication och filen kan ingå i ett ASP.NET Core-program. Eftersom den heter Globalkan följande kod användas för att registrera den:

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

Så länge logiken i den är tillgänglig i ASP.NET Core kan den här metoden användas för att stegvis migrera beroendet av Global.asax till ASP.NET Core.

Autentiserings-/auktoriseringshändelser

För att autentiserings- och auktoriseringshändelserna ska kunna köras vid önskad tidpunkt bör följande mönster användas:

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

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

Om detta inte görs körs händelserna fortfarande. Det kommer dock att ske under anropet av .UseSystemWebAdapters().

HTTP-modulpool

Eftersom moduler och program i ASP.NET Framework har tilldelats en begäran behövs en ny instans för varje begäran. Men eftersom de kan vara dyra att skapa, sammanförs de med hjälp av ObjectPool<T>. För att anpassa den faktiska livslängden för HttpApplication instanserna kan en anpassad pool användas:

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

Ytterligare resurser