Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Artikel ini menunjukkan cara memigrasikan modul HTTP ASP.NET yang ada dari system.webserver ke middleware ASP.NET Core.
Modul yang ditinjau kembali
Sebelum melanjutkan ke middleware ASP.NET Core, mari kita rekap terlebih dahulu cara kerja modul HTTP:
Modulnya adalah:
Kelas yang mengimplementasikan IHttpModule
Dipanggil untuk setiap permintaan
Dapat melakukan sirkuit pendek (hentikan pemrosesan permintaan lebih lanjut)
Dapat menambahkan ke respons HTTP, atau membuat respons HTTP mereka sendiri
Dikonfigurasi dalam Web.config
Urutan proses modul permintaan masuk ditentukan oleh:
Serangkaian peristiwa yang dipicu oleh ASP.NET, seperti BeginRequest dan AuthenticateRequest. Untuk daftar lengkapnya, lihat System.Web.HttpApplication. Setiap modul dapat membuat handler untuk satu atau beberapa peristiwa.
Untuk peristiwa yang sama, urutan konfigurasinya di Web.config.
Selain modul, Anda dapat menambahkan handler untuk peristiwa siklus hidup ke file Anda Global.asax.cs . Handler ini berjalan setelah handler dalam modul yang dikonfigurasi.
Dari modul ke middleware
Middleware lebih sederhana daripada modul HTTP:
Modul,
Global.asax.cs, Web.config (kecuali untuk konfigurasi IIS) dan siklus hidup aplikasi hilangPeran modul telah diambil alih oleh middleware
Middleware dikonfigurasi menggunakan kode daripada di Web.config
- Pencabangan alur memungkinkan Anda mengirim permintaan ke middleware tertentu, berdasarkan tidak hanya URL tetapi juga pada header permintaan, string kueri, dll.
- Pencabangan alur memungkinkan Anda mengirim permintaan ke middleware tertentu, berdasarkan tidak hanya URL tetapi juga pada header permintaan, string kueri, dll.
Middleware sangat mirip dengan modul:
Dipanggil secara prinsip untuk setiap permintaan
Dapat memutus aliran permintaan, dengan tidak meneruskan ke middleware berikutnya
Mampu membuat respons HTTP mereka sendiri
Middleware dan modul diproses dalam urutan yang berbeda:
Urutan middleware didasarkan pada urutan di mana mereka dimasukkan ke dalam alur permintaan, sementara urutan modul terutama didasarkan pada System.Web.HttpApplication peristiwa.
Urutan middleware untuk respons adalah kebalikan dari urutan untuk permintaan, sedangkan urutan modul tetap sama baik untuk permintaan maupun respons.
Perhatikan bagaimana pada gambar di atas, middleware autentikasi memutus permintaan.
Memigrasikan kode modul ke middleware
Modul HTTP yang ada akan terlihat mirip dengan ini:
// 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.
}
}
}
Seperti yang ditunjukkan di halaman Middleware, middleware ASP.NET Core adalah kelas yang memperkenalkan metode yang menerima Invoke dan mengembalikan HttpContext. Middleware baru Anda akan terlihat seperti ini:
// 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>();
}
}
}
Templat middleware sebelumnya diambil dari bagian tentang menulis middleware.
Kelas pembantu MyMiddlewareExtensions memudahkan konfigurasi middleware Anda di kelas Anda Startup . Metode UseMyMiddleware ini menambahkan kelas middleware Anda ke alur permintaan. Layanan yang dibutuhkan oleh middleware disuntikkan ke dalam konstruktor middleware.
Modul Anda mungkin mengakhiri permintaan, misalnya jika pengguna tidak diotorisasi:
// 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;
}
}
Middleware menangani ini dengan tidak memanggil Invoke pada middleware berikutnya di pipeline. Perlu diingat bahwa ini tidak sepenuhnya mengakhiri proses permintaan, karena middleware yang sebelumnya akan tetap dieksekusi ketika respons kembali melalui alur.
// 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.
}
Saat memigrasikan fungsionalitas modul ke middleware baru, Anda mungkin menemukan bahwa kode Anda tidak dikompilasi karena HttpContext kelas telah berubah secara signifikan di ASP.NET Core. Lihat Migrasi dari ASP.NET Framework HttpContext ke ASP.NET Core untuk mempelajari cara bermigrasi ke ASP.NET Core HttpContext baru.
Memigrasikan penyisipan modul ke dalam alur permintaan
Modul HTTP biasanya ditambahkan ke alur permintaan menggunakan 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>
Konversikan ini dengan menambahkan middleware baru Anda ke alur permintaan di kelas Anda 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?}");
});
}
Tempat yang tepat dalam alur tempat Anda memasukkan middleware baru tergantung pada peristiwa yang ditanganinya sebagai modul (BeginRequest, , EndRequestdll.) dan urutannya dalam daftar modul Anda di Web.config.
Seperti yang dinyatakan sebelumnya, tidak ada siklus hidup aplikasi di ASP.NET Core dan urutan di mana respons diproses oleh middleware berbeda dari urutan yang digunakan oleh modul. Ini bisa membuat keputusan pemesanan Anda lebih menantang.
Jika pemesanan menjadi masalah, Anda dapat membagi modul Anda menjadi beberapa komponen middleware yang dapat dipesan secara independen.
Memuat opsi middleware menggunakan pola opsi
Beberapa modul memiliki opsi konfigurasi yang disimpan di Web.config. Namun, di ASP.NET Core, model konfigurasi baru digunakan sebagai pengganti Web.config.
Sistem konfigurasi baru memberi Anda opsi ini untuk menyelesaikan ini:
Langsung masukkan opsi ke middleware, seperti yang ditunjukkan di bagian berikutnya.
Gunakan pola opsi:
Buat kelas untuk menyimpan opsi middleware Anda, misalnya:
public class MyMiddlewareOptions { public string Param1 { get; set; } public string Param2 { get; set; } }Menyimpan nilai opsi
Sistem konfigurasi memungkinkan Anda menyimpan nilai opsi di mana pun Anda inginkan. Namun, sebagian besar situs menggunakan
appsettings.json, jadi kita akan mengambil pendekatan itu:{ "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }MyMiddlewareOptionsSection di sini adalah nama bagian. Tidak harus sama dengan nama kelas opsi Anda.
Mengaitkan nilai opsi dengan kelas opsi
Pola opsi menggunakan ASP.NET kerangka kerja injeksi dependensi Core untuk mengaitkan jenis opsi (seperti
MyMiddlewareOptions) denganMyMiddlewareOptionsobjek yang memiliki opsi aktual.Perbarui kelas Anda
Startup:Jika Anda menggunakan
appsettings.json, tambahkan ke penyusun konfigurasi diStartupkonstruktor: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(); }Konfigurasikan layanan opsi:
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(); }Kaitkan opsi Anda dengan kelas opsi Anda:
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(); }
Masukkan opsi ke konstruktor middleware Anda. Ini mirip dengan menyuntikkan opsi ke pengontrol.
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 } }Metode ekstensi UseMiddleware yang menambahkan middleware Anda ke
IApplicationBuildermengurus injeksi dependensi.Ini tidak terbatas pada
IOptionsobjek. Objek lain yang diperlukan middleware Anda dapat disuntikkan dengan cara ini.
Memuat opsi middleware melalui injeksi langsung
Pola opsi memiliki keuntungan karena menciptakan kopling longgar antara nilai opsi dan konsumennya. Setelah Anda mengaitkan kelas opsi dengan nilai opsi aktual, kelas lain apa pun bisa mendapatkan akses ke opsi melalui kerangka kerja injeksi dependensi. Tidak perlu meneruskan nilai opsi.
Namun, ini tidak berfungsi jika Anda ingin menggunakan middleware yang sama dua kali dengan opsi yang berbeda. Misalnya middleware otorisasi yang digunakan di cabang yang berbeda yang memungkinkan peran yang berbeda. Anda tidak dapat mengaitkan dua objek opsi yang berbeda dengan satu kelas opsi.
Solusinya adalah mendapatkan objek opsi dengan nilai opsi aktual di kelas Anda Startup dan meneruskan objek tersebut langsung ke setiap instans middleware Anda.
Menambahkan kunci kedua ke
appsettings.jsonUntuk menambahkan sekumpulan opsi kedua ke
appsettings.jsonfile, gunakan kunci baru untuk mengidentifikasinya secara unik:{ "MyMiddlewareOptionsSection2": { "Param1": "Param1Value2", "Param2": "Param2Value2" }, "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }Ambil nilai opsi dan teruskan ke middleware. Metode ekstensi
Use...(yang menambahkan middleware Anda ke pipeline) adalah tempat yang logis untuk meneruskan nilai opsi.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?}"); }); }Aktifkan middleware untuk mengambil parameter opsi. Berikan overload dari metode ekstensi
Use...(yang menerima parameter opsi dan meneruskannya keUseMiddleware). KetikaUseMiddlewaredipanggil dengan parameter, ia meneruskan parameter ke konstruktor middleware Anda saat membuat instans objek 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)); } }Perhatikan bagaimana ini membungkus objek opsi dalam
OptionsWrapperobjek. Ini mengimplementasikanIOptions, seperti yang diharapkan oleh konstruktor middleware.
Migrasi IHttpModule bertahap
Ada kalanya mengonversi modul ke middleware tidak dapat dengan mudah dilakukan. Untuk mendukung skenario migrasi di mana modul diperlukan dan tidak dapat dipindahkan ke middleware, adaptor System.Web mendukung penambahan modul ke ASP.NET Core.
Contoh IHttpModule
Untuk mendukung modul, instans HttpApplication harus tersedia. Jika tidak ada HttpApplication khusus yang digunakan, satu default akan digunakan untuk menambahkan modul. Peristiwa yang dideklarasikan dalam aplikasi kustom (termasuk Application_Start) akan didaftarkan dan dijalankan sesuai.
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()
{
}
}
Migrasi Global.asax
Infrastruktur ini dapat digunakan untuk memigrasikan penggunaan Global.asax jika diperlukan. Sumber dari Global.asax adalah kustom HttpApplication dan file dapat disertakan dalam aplikasi ASP.NET Core. Karena diberi nama Global, kode berikut dapat digunakan untuk mendaftarkannya:
builder.Services.AddSystemWebAdapters()
.AddHttpApplication<Global>();
Selama logika di dalamnya tersedia di ASP.NET Core, pendekatan ini dapat digunakan untuk memigrasikan keandalan Global.asax secara bertahap ke ASP.NET Core.
Peristiwa autentikasi/Otorisasi
Agar peristiwa autentikasi dan otorisasi berjalan pada waktu yang diinginkan, pola berikut harus digunakan:
app.UseAuthentication();
app.UseAuthenticationEvents();
app.UseAuthorization();
app.UseAuthorizationEvents();
Jika ini tidak dilakukan, peristiwa akan tetap berjalan. Namun, hal itu akan terjadi selama panggilan .UseSystemWebAdapters().
Pengumpulan Modul HTTP
Karena modul dan aplikasi dalam ASP.NET Framework ditetapkan ke permintaan, instans baru diperlukan untuk setiap permintaan. Namun, karena mereka bisa mahal untuk dibuat, mereka dikumpulkan menggunakan ObjectPool<T>. Untuk menyesuaikan masa pakai yang sebenarnya dari instans HttpApplication, kumpulan kustom dapat digunakan:
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);
});
Sumber daya tambahan
ASP.NET Core