이 문서에서는 기존 ASP.NET HTTP 모듈을 system.webserver에서 ASP.NET Core 미들웨어로 마이그레이션하는 방법을 보여 줍니다.
다시 검토된 모듈
ASP.NET Core 미들웨어에 대해 설명하기 전에, 먼저 HTTP 모듈의 작동 방식을 다시 살펴보겠습니다.
모듈은 다음과 같습니다.
구현하는 클래스 IHttpModule
모든 요청에 대해 호출됨
단락 가능(요청의 추가 처리 중지)
HTTP 응답에 추가하거나 직접 만들 수 있습니다.
구성됨 in Web.config
모듈이 들어오는 요청을 처리하는 순서는 다음을 통해 결정됩니다.
ASP.NET에서 발생한 일련의 이벤트(예: BeginRequest 및 AuthenticateRequest). 전체 목록은 다음을 참조하세요 System.Web.HttpApplication. 각 모듈은 하나 이상의 이벤트에 대한 처리기를 만들 수 있습니다.
동일한 이벤트의 경우, Web.config에서 구성되는 순서입니다.
모듈 외에도 수명 주기 이벤트에 대한 처리기를 파일에 추가할 수 있습니다 Global.asax.cs . 이러한 처리기는 구성된 모듈의 처리기 후에 실행됩니다.
모듈에서 미들웨어로
미들웨어는 HTTP 모듈보다 간단합니다.
모듈,
Global.asax.csWeb.config (IIS 구성 제외) 및 애플리케이션 수명 주기가 사라집니다.미들웨어에서 모듈의 역할을 인수했습니다.
미들웨어는 Web.config 대신 코드를 사용하여 구성됩니다.
- 파이프라인 분기를 사용하면 URL뿐만 아니라 요청 헤더, 쿼리 문자열 등에 따라 특정 미들웨어에 요청을 보낼 수 있습니다.
- 파이프라인 분기를 사용하면 URL뿐만 아니라 요청 헤더, 쿼리 문자열 등에 따라 특정 미들웨어에 요청을 보낼 수 있습니다.
미들웨어는 모듈과 매우 유사합니다.
모든 요청에 대해 원칙에 따라 호출됨
자체 HTTP 응답을 만들 수 있습니다.
미들웨어 및 모듈은 다른 순서로 처리됩니다.
미들웨어 순서는 요청 파이프라인에 삽입되는 순서를 기반으로 하지만 모듈 순서는 주로 이벤트를 기반으로 합니다 System.Web.HttpApplication .
응답에 대한 미들웨어 순서는 요청의 경우와 반대이지만 모듈 순서는 요청 및 응답에 대해 동일합니다.
위의 이미지에서 인증 미들웨어가 요청을 단락하는 방법을 확인합니다.
모듈 코드를 미들웨어로 마이그레이션
기존 HTTP 모듈은 다음과 유사합니다.
// 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.
}
}
}
ASP.NET Core 미들웨어는 미들웨어 페이지에 표시된 것처럼 Invoke를 매개변수로 받아 HttpContext를 반환하는 Task 메서드를 노출하는 클래스입니다. 새 미들웨어는 다음과 같습니다.
// 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>();
}
}
}
이전 미들웨어 템플릿은 미들웨어 작성 섹션에서 가져온 것입니다.
MyMiddlewareExtensions 도우미 클래스를 사용하면 클래스에서 미들웨어를 보다 쉽게 구성할 수 있습니다Startup. 메서드는 UseMyMiddleware 요청 파이프라인에 미들웨어 클래스를 추가합니다. 미들웨어에 필요한 서비스는 미들웨어의 생성자에 삽입됩니다.
모듈에서 요청을 종료할 수 있습니다(예: 사용자에게 권한이 없는 경우).
// 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;
}
}
미들웨어는 파이프라인의 다음 미들웨어를 호출 Invoke 하지 않음으로써 이를 처리합니다. 응답이 파이프라인을 통해 돌아갈 때 이전 미들웨어가 계속 호출되기 때문에 요청이 완전히 종료되지는 않습니다.
// 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.
}
모듈의 기능을 새 미들웨어로 마이그레이션하는 경우 ASP.NET Core에서 클래스가 크게 변경되었기 때문에 HttpContext 코드가 컴파일되지 않을 수 있습니다.
새 ASP.NET Core HttpContext로 마이그레이션하는 방법을 알아보려면 ASP.NET Framework HttpContext에서 ASP.NET Core로 마이그레이션을 참조하세요.
요청 파이프라인으로 모듈 삽입 마이그레이션
HTTP 모듈은 일반적으로 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>
클래스의 요청 파이프라인에 새 미들웨어를 추가하여 이를 변환합니다 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?}");
});
}
새 미들웨어를 삽입하는 파이프라인의 정확한 지점은 모듈(BeginRequestEndRequest등)으로 처리된 이벤트와 Web.config모듈 목록에서의 순서에 따라 달라집니다.
앞에서 설명한 것처럼 ASP.NET Core에는 애플리케이션 수명 주기가 없으며 응답이 미들웨어에서 처리되는 순서는 모듈에서 사용하는 순서와 다릅니다. 이렇게 하면 주문 결정이 더 어려워질 수 있습니다.
순서 지정이 문제가 되면 모듈을 독립적으로 정렬할 수 있는 여러 미들웨어 구성 요소로 분할할 수 있습니다.
옵션 패턴을 사용하여 미들웨어 옵션 로드
일부 모듈에는 Web.config저장된 구성 옵션이 있습니다. 그러나 ASP.NET Core에서는Web.config대신 새 구성 모델이 사용됩니다.
새 구성 시스템은 이 문제를 해결하기 위해 다음과 같은 옵션을 제공합니다.
다음 섹션과 같이 미들웨어에 옵션을 직접 삽입합니다.
미들웨어 옵션을 저장할 클래스를 만듭니다. 예를 들면 다음과 같습니다.
public class MyMiddlewareOptions { public string Param1 { get; set; } public string Param2 { get; set; } }옵션 값 저장
구성 시스템을 사용하면 원하는 위치에 옵션 값을 저장할 수 있습니다. 그러나 대부분의 사이트에서는 다음 방법을 사용합니다
appsettings.json.{ "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }MyMiddlewareOptionsSection 은 섹션 이름입니다. 옵션 클래스의 이름과 같을 필요는 없습니다.
옵션 값을 옵션 클래스와 연결
옵션 패턴은 ASP.NET Core의 종속성 주입 프레임워크를 사용하여 옵션 형식(예:
MyMiddlewareOptions)을 실제 옵션이 있는 개체와MyMiddlewareOptions연결합니다.당신의
Startup클래스를 업데이트하세요.사용 중인
appsettings.json를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(); }옵션 서비스를 구성합니다.
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(); }옵션을 옵션 클래스와 연결합니다.
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(); }
미들웨어 생성자에 옵션을 삽입합니다. 이는 컨트롤러에 옵션을 삽입하는 것과 유사합니다.
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 확장 메서드는 미들웨어를
IApplicationBuilder에 추가하고, 종속성 주입을 처리합니다.IOptions개체에만 국한되지 않습니다. 미들웨어에 필요한 다른 모든 개체를 이러한 방식으로 삽입할 수 있습니다.
직접 주입을 통해 미들웨어 옵션 로드
옵션 패턴은 옵션 값과 소비자 간에 느슨한 결합을 만들 수 있다는 장점이 있습니다. 옵션 클래스를 실제 옵션 값과 연결하면 다른 클래스는 종속성 주입 프레임워크를 통해 옵션에 액세스할 수 있습니다. 옵션 값을 전달할 필요가 없습니다.
동일한 미들웨어를 다른 옵션으로 두 번 사용하려고 하면 문제가 발생할 수 있습니다. 예를 들어, 서로 다른 브랜치에서 사용되고 다양한 역할을 허용하는 권한 부여 미들웨어입니다. 두 개의 서로 다른 옵션 개체를 하나의 옵션 클래스와 연결할 수 없습니다.
솔루션은 클래스의 실제 옵션 값이 있는 Startup 옵션 개체를 가져와서 미들웨어의 각 인스턴스에 직접 전달하는 것입니다.
에 두 번째 키 추가
appsettings.json파일에 두 번째 옵션 집합을 추가하려면 새 키를 사용하여 이를 고유하게 식별하십시오.
{ "MyMiddlewareOptionsSection2": { "Param1": "Param1Value2", "Param2": "Param2Value2" }, "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }옵션 값을 검색하고 미들웨어에 전달합니다.
Use...파이프라인에 미들웨어를 추가하는 확장 메서드는 옵션 값을 전달할 논리적 위치입니다.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?}"); }); }미들웨어가 옵션 매개 변수를 사용하도록 설정합니다. 옵션 매개 변수를 받고
Use...에 전달하는UseMiddleware확장 메서드의 오버로드를 제공합니다.UseMiddleware매개 변수를 사용하여 호출되면 미들웨어 개체를 인스턴스화할 때 매개 변수를 미들웨어 생성자에 전달합니다.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)); } }이렇게 하면 옵션 객체를
OptionsWrapper객체로 래핑한다. 미들웨어 생성자가 예상하는 대로IOptions를 구현합니다.
점진적 IHttpModule 마이그레이션
모듈을 미들웨어로 쉽게 변환할 수 없는 경우가 있습니다. 모듈이 필요하고 미들웨어로 이동할 수 없는 마이그레이션 시나리오를 지원하기 위해 System.Web 어댑터는 ASP.NET Core에 추가하도록 지원합니다.
IHttpModule 예제
모듈을 지원하려면 인스턴스 HttpApplication 를 사용할 수 있어야 합니다. 사용자 지정 HttpApplication 을 사용하지 않으면 모듈을 추가하는 데 기본값이 사용됩니다. 사용자 지정 애플리케이션에 선언된 이벤트(포함 Application_Start)가 등록되고 그에 따라 실행됩니다.
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 마이그레이션
이 인프라는 필요한 경우 사용량을 Global.asax 마이그레이션하는 데 사용할 수 있습니다. 원본 Global.asax 은 사용자 지정 HttpApplication 이며 ASP.NET Core 애플리케이션에 파일을 포함할 수 있습니다. 이름이 지정 Global되므로 다음 코드를 사용하여 등록할 수 있습니다.
builder.Services.AddSystemWebAdapters()
.AddHttpApplication<Global>();
ASP.NET Core에서 논리를 사용할 수 있는 한 이 방법을 사용하여 ASP.NET Core에 대한 Global.asax 의존도를 증분 방식으로 마이그레이션할 수 있습니다.
인증/권한 부여 이벤트
인증 및 권한 부여 이벤트를 원하는 시간에 실행하려면 다음 패턴을 사용해야 합니다.
app.UseAuthentication();
app.UseAuthenticationEvents();
app.UseAuthorization();
app.UseAuthorizationEvents();
이 작업이 수행되지 않으면 이벤트가 계속 실행됩니다. 그러나 .UseSystemWebAdapters()을 호출하는 동안 실행됩니다.
HTTP 모듈 풀링
ASP.NET Framework의 모듈과 애플리케이션이 요청에 할당되었으므로 각 요청에 새 인스턴스가 필요합니다. 그러나 만드는 데 비용이 많이 들 수 있으므로 ObjectPool<T>를 사용하여 공동으로 모아 사용합니다. 인스턴스의 HttpApplication 실제 수명을 사용자 지정하기 위해 사용자 지정 풀을 사용할 수 있습니다.
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);
});
추가 리소스
ASP.NET Core