사용자 지정 미들웨어 작성
Use/Run/Map으로 인라인 미들웨어를 만들고 InvokeAsync를 사용하는 클래스 기반 미들웨어로 재사용 가능한 구성 요소를 구현합니다.
사용자 지정 미들웨어 작성은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
사용자 지정 미들웨어가 필요한 이유
사용자 지정 미들웨어는 여러 요청에 적용되는 횡단 관심사를 처리합니다. 예를 들면 로깅, 시간 측정, 오류 처리, 인증, 압축, 캐싱 등이 있습니다. 한 번 작성하면 일치하는 모든 요청에 해당 논리가 자동으로 실행됩니다.
Use를 사용한 인라인 미들웨어
미들웨어를 추가하는 가장 빠른 방법은 app.Use()에 대리자를 전달하는 것입니다. 프로토타이핑이나 정말 단순한 논리에 적합합니다.
app.Use(async (context, next) =>
{
var start = DateTimeOffset.UtcNow;
await next(context);
var elapsed = DateTimeOffset.UtcNow - start;
context.Response.Headers.Append(
"X-Elapsed", elapsed.TotalMilliseconds.ToString("F0") + "ms");
});클래스 기반 미들웨어(관례)
관례 기반 미들웨어 클래스에는 RequestDelegate를 받는 생성자와 public InvokeAsync 메서드가 있습니다. 추가 생성자 매개 변수는 DI에서 주입할 수 있습니다.
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
var headers = context.Response.Headers;
headers.Append("X-Content-Type-Options", "nosniff");
headers.Append("X-Frame-Options", "DENY");
headers.Append("X-XSS-Protection", "1; mode=block");
headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
await _next(context);
}
}
// Extension method for clean registration:
public static class SecurityHeadersExtensions
{
public static IApplicationBuilder UseSecurityHeaders(
this IApplicationBuilder app) =>
app.UseMiddleware<SecurityHeadersMiddleware>();
}DI 범위 수명 의존성을 위한 IMiddleware
미들웨어에 DbContext와 같은 범위 지정 서비스가 필요하다면 IMiddleware를 구현하세요. 이를 범위 수명으로 등록하면 프레임워크가 요청마다 새 인스턴스를 확인합니다.
public class AuditMiddleware : IMiddleware
{
private readonly AuditDbContext _db;
private readonly IHttpContextAccessor _http;
public AuditMiddleware(AuditDbContext db, IHttpContextAccessor http)
{
_db = db;
_http = http;
}
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
await next(ctx);
if (ctx.Request.Method != "GET")
{
_db.AuditLogs.Add(new AuditLog
{
User = ctx.User.Identity?.Name,
Path = ctx.Request.Path,
Status = ctx.Response.StatusCode,
At = DateTime.UtcNow
});
await _db.SaveChangesAsync();
}
}
}
builder.Services.AddScoped<AuditMiddleware>();
app.UseMiddleware<AuditMiddleware>();옵션을 사용하는 미들웨어
생성자의 옵션 객체를 통해 미들웨어에 구성을 전달하세요. DI 컨테이너에 옵션을 등록하거나 미들웨어를 등록할 때 인라인으로 전달할 수 있습니다.
public class ThrottleOptions
{
public int MaxRequestsPerSecond { get; set; } = 100;
public string[] ExcludedPaths { get; set; } = Array.Empty<string>();
}
public class ThrottleMiddleware
{
private readonly RequestDelegate _next;
private readonly ThrottleOptions _options;
public ThrottleMiddleware(RequestDelegate next, ThrottleOptions options)
{
_next = next;
_options = options;
}
// ...
}
// Registration with options:
app.UseMiddleware<ThrottleMiddleware>(new ThrottleOptions
{
MaxRequestsPerSecond = 50
});예외 처리 미들웨어
사용자 지정 예외 처리 미들웨어는 처리되지 않은 모든 예외를 포착하고 구조화된 오류 응답을 반환합니다.
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> l)
{
_next = next;
_logger = l;
}
public async Task InvokeAsync(HttpContext ctx)
{
try { await _next(ctx); }
catch (NotFoundException ex)
{
ctx.Response.StatusCode = 404;
await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception");
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsJsonAsync(new { error = "Internal server error" });
}
}
}요청 로깅 미들웨어
구조화된 요청/응답 로깅 미들웨어는 모든 요청에 대해 메서드, 경로, 상태 코드, 경과 시간을 기록합니다.
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _log;
public RequestLoggingMiddleware(RequestDelegate n, ILogger<RequestLoggingMiddleware> l)
{ _next = n; _log = l; }
public async Task InvokeAsync(HttpContext ctx)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
await _next(ctx);
sw.Stop();
_log.LogInformation(
"{Method} {Path} -> {Status} in {Ms}ms",
ctx.Request.Method,
ctx.Request.Path,
ctx.Response.StatusCode,
sw.ElapsedMilliseconds);
}
}UseWhen을 사용한 조건부 미들웨어
UseWhen은 조건식에 따라 파이프라인을 조건부로 분기하며, 파이프라인을 영구적으로 분리하지는 않습니다. 분기된 파이프라인은 이후 주 파이프라인으로 다시 합쳐집니다.
// Apply authentication middleware only to /api/* routes
app.UseWhen(
ctx => ctx.Request.Path.StartsWithSegments("/api"),
apiApp =>
{
apiApp.UseAuthentication();
apiApp.UseAuthorization();
});
// Public routes (/public/*) bypass authentication
app.MapGet("/public/health", () => "OK");
app.MapGet("/api/data", [Authorize] async (DataService s) => await s.GetAsync());응답 캐싱 미들웨어
기본 제공 응답 캐싱 미들웨어는 전체 응답을 메모리에 저장합니다. 헤더나 특성을 사용해 엔드포인트별 캐시 정책을 구성하세요.
builder.Services.AddResponseCaching();
app.UseResponseCaching();
app.MapGet("/products", async (AppDbContext db) =>
{
var products = await db.Products.AsNoTracking().ToListAsync();
return Results.Ok(products);
})
.CacheOutput(p => p.Expire(TimeSpan.FromMinutes(2)).Tag("products"));실제 사례: 유지 관리 모드 미들웨어
구성에서 플래그를 읽고 IOptionsMonitor를 통한 핫 리로드를 지원하며, 유지 관리 중에는 상태 확인이 아닌 요청을 거부하는 유지 관리 모드 미들웨어입니다.
public class MaintenanceMiddleware
{
private readonly RequestDelegate _next;
private readonly IOptionsMonitor<MaintenanceOptions> _options;
public MaintenanceMiddleware(RequestDelegate next,
IOptionsMonitor<MaintenanceOptions> opts)
{ _next = next; _options = opts; }
public async Task InvokeAsync(HttpContext ctx)
{
if (_options.CurrentValue.IsEnabled
&& !ctx.Request.Path.StartsWithSegments("/health"))
{
ctx.Response.StatusCode = 503;
ctx.Response.Headers.Append("Retry-After", "300");
await ctx.Response.WriteAsJsonAsync(
new { message = "Service under maintenance" });
return;
}
await _next(ctx);
}
}빠른 확인
DbContext와 같은 범위 지정 서비스가 필요할 때 관례 기반 방식 대신 IMiddleware를 구현해야 하는 이유는 무엇인가요?
복습: 사용자 지정 미들웨어 작성
핵심 요점:
- 인라인(Use 대리자): 빠르고 한 줄로 작성할 수 있으며, 클래스 기반 방식은 재사용과 테스트에 적합합니다
- 관례 기반 클래스: 생성자가 RequestDelegate를 받고 InvokeAsync(HttpContext)를 구현합니다
- IMiddleware: 요청마다 DI에서 확인되므로 범위 지정 의존성에 필요합니다
- UseWhen: 주 파이프라인으로 다시 합쳐지는 조건부 분기를 제공합니다
- 모든 오류를 포착하려면 예외 처리 미들웨어를 가장 먼저(가장 바깥쪽에) 작성합니다
- 깔끔하고 쉽게 찾을 수 있는 등록을 위해 확장 메서드(UseXxx)를 만듭니다
자주 묻는 질문
“사용자 지정 미들웨어 작성” 강의는 무료인가요?
네 — “사용자 지정 미들웨어 작성” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“사용자 지정 미들웨어 작성”에서 뭘 배우나요?
Use/Run/Map으로 인라인 미들웨어를 만들고 InvokeAsync를 사용하는 클래스 기반 미들웨어로 재사용 가능한 구성 요소를 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“사용자 지정 미들웨어 작성” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- ASP.NET Core 파이프라인 개요
- 사용자 지정 미들웨어 작성
- 단락 처리와 분기
- 미들웨어 순서와 기본 제공 미들웨어