다음을 통해 공유


ASP.NET Core에서 오류 처리

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Warning

이 버전의 ASP.NET Core는 더 이상 지원되지 않습니다. 자세한 내용은 .NET 및 .NET Core 지원 정책을 참조 하세요. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

작성자: Tom Dykstra

이 항목에서는 ASP.NET Core 웹앱에서 오류를 처리하는 일반적인 접근법을 다룹니다. ASP.NET Core 컨트롤러 기반 웹 API의 오류 처리 및 최소 API의 오류 처리도 참조하세요.

Blazor 이 문서의 지침을 추가하거나 대체하는 오류 처리 지침은 ASP.NET Core Blazor 앱의 오류 처리를 참조하세요.

개발자 예외 페이지

개발자 예외 페이지에는 처리되지 않은 요청 예외에 대한 자세한 정보가 표시됩니다. 이것은 DeveloperExceptionPageMiddleware를 사용하여 HTTP 파이프라인에서 동기 및 비동기 예외를 캡처하고 오류 응답을 생성합니다. 개발자 예외 페이지는 미들웨어 파이프라인의 앞부분에 실행되므로, 다음에 오는 미들웨어에서 throw된 미처리 예외를 catch할 수 있습니다.

ASP.NET Core 앱은 다음과 같은 경우 기본적으로 개발자 예외 페이지를 사용할 수 있습니다.

이전 템플릿을 사용하여 만든 앱, 즉, 사용하여 WebHost.CreateDefaultBuilder개발자 예외 페이지를 호출 app.UseDeveloperExceptionPage하여 사용하도록 설정할 수 있습니다.

Warning

앱이 개발 환경에서 실행 중인 경우에만 개발자 예외 페이지를 사용하도록 설정하지 마세요. 프로덕션 환경에서 앱을 실행할 때 자세한 예외 정보를 공개적으로 공유하지 마세요. 환경을 구성하는 방법에 대한 자세한 내용은 ASP.NET Core에서 여러 환경 사용을 참조하세요.

개발자 예외 페이지에는 예외 및 요청에 대한 다음 정보가 포함될 수 있습니다.

  • 스택 추적
  • 쿼리 문자열 매개 변수(있는 경우)
  • 쿠키(있는 경우)
  • 헤더
  • 엔드포인트 메타데이터(있는 경우)

개발자 예외 페이지는 어떠한 정보 제공도 보장하지 않습니다. 전체 오류 정보를 보려면 로깅을 사용하세요.

다음 이미지는 탭 및 표시되는 정보를 표시하는 애니메이션이 있는 샘플 개발자 예외 페이지를 보여 줍니다.

선택한 각 탭을 표시하기 위해 애니메이션 효과를 준 개발자 예외 페이지입니다.

헤더가 있는 요청에 Accept: text/plain 대한 응답으로 개발자 예외 페이지는 HTML 대신 일반 텍스트를 반환합니다. 예시:

Status: 500 Internal Server Error
Time: 9.39 msSize: 480 bytes
FormattedRawHeadersRequest
Body
text/plain; charset=utf-8, 480 bytes
System.InvalidOperationException: Sample Exception
   at WebApplicationMinimal.Program.<>c.<Main>b__0_0() in C:\Source\WebApplicationMinimal\Program.cs:line 12
   at lambda_method1(Closure, Object, HttpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS
=======
Accept: text/plain
Host: localhost:7267
traceparent: 00-0eab195ea19d07b90a46cd7d6bf2f

예외 처리기 페이지

프로덕션 환경의 사용자 지정 오류 처리 페이지를 구성하려면 UseExceptionHandler를 호출합니다. 이 예외 처리 미들웨어에서 다음을 수행합니다.

  • 미처리 예외를 catch하고 기록합니다.
  • 대체 파이프라인에서 표시된 경로를 사용하여 요청을 다시 실행합니다. 응답이 시작된 경우에는 요청이 다시 실행되지 않습니다. 템플릿 생성 코드는 /Error 경로를 사용하여 요청을 다시 실행합니다.

Warning

대체 파이프라인이 자체 예외를 throw하는 경우 예외 처리 미들웨어가 원래 예외를 다시 throw합니다.

이 미들웨어는 요청 파이프라인을 다시 실행할 수 있으므로 다음을 수행합니다.

  • 미들웨어는 동일한 요청으로 재진입을 처리해야 합니다. 이는 일반적으로 _next를 호출한 후 상태를 정리하거나 HttpContext에서 처리를 캐싱하여 다시 실행하지 않도록 하는 것을 의미합니다. 요청 본문을 처리할 때 폼 판독기처럼 결과를 버퍼링하거나 캐싱하는 것을 의미합니다.
  • 템플릿에 사용되는 UseExceptionHandler(IApplicationBuilder, String) 오버로드의 경우 요청 경로만 수정되고 경로 데이터가 지워집니다. 헤더, 메서드 및 항목과 같은 요청 데이터는 모두 있는 그대로 재사용됩니다.
  • 범위가 지정된 서비스는 동일하게 유지됩니다.

다음 예제에서 UseExceptionHandler는 개발 환경이 아닌 환경에서 예외 처리 미들웨어를 추가합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Razor Pages 앱 템플릿은 Pages 폴더에 오류 페이지(.cshtmlPageModel클래스ErrorModel)를 제공합니다. MVC 앱의 프로젝트 템플릿에는 Home 컨트롤러에 대한 Error 작업 메서드와 오류 보기가 포함됩니다.

예외 처리 미들웨어는 원래 HTTP 메서드를 사용하여 요청을 다시 실행합니다. 오류 처리기 엔드포인트가 특정 HTTP 메서드 집합으로 제한되는 경우 해당 HTTP 메서드에 대해서만 실행됩니다. 예를 들어 [HttpGet] 특성을 사용하는 MVC 컨트롤러 작업은 GET 요청에 대해서만 실행됩니다. 모든 요청이 사용자 지정 오류 처리 페이지에 도달하도록 하려면 특정 HTTP 메서드 집합으로 제한하지 마세요.

원래 HTTP 메서드에 따라 예외를 서로 다르게 처리하려면 다음을 수행합니다.

  • Razor Pages의 경우 여러 처리기 메서드를 만듭니다. 예를 들어 OnGet을 사용하여 GET 예외를 처리하고 OnPost를 사용하여 POST 예외를 처리합니다.
  • MVC의 경우 여러 작업에 HTTP 동사 특성을 적용합니다. 예를 들어 [HttpGet]을 사용하여 GET 예외를 처리하고 [HttpPost]를 사용하여 POST 예외를 처리합니다.

인증되지 않은 사용자가 사용자 지정 오류 처리 페이지를 볼 수 있도록 하려면 익명 액세스를 지원해야 합니다.

예외에 액세스

IExceptionHandlerPathFeature를 사용하여 오류 처리기에서 예외 및 원래 요청 경로에 액세스합니다. 다음 예제에서는 IExceptionHandlerPathFeature를 사용하여 throw된 예외에 대한 자세한 정보를 가져옵니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

예외 처리기 람다

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 응답을 반환하기 전에 오류에 액세스할 수 있습니다.

다음 코드에서는 예외 처리에 람다를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

람다를 사용하는 또 다른 방법은 다음 예제와 같이 예외 형식에 따라 상태 코드를 설정하는 것입니다.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        StatusCodeSelector = ex => ex is TimeoutException
            ? StatusCodes.Status503ServiceUnavailable
            : StatusCodes.Status500InternalServerError
    });
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

IExceptionHandler

IExceptionHandler 는 개발자에게 중앙 위치에서 알려진 예외를 처리하기 위한 콜백을 제공하는 인터페이스입니다.

IExceptionHandler 구현은 호출 IServiceCollection.AddExceptionHandler<T>하여 등록됩니다. 인스턴스의 수 IExceptionHandler 명은 싱글톤입니다. 여러 구현을 추가할 수 있으며 등록된 순서대로 호출됩니다.

예외 처리기가 요청을 처리하는 경우 처리를 중지하도록 반환 true 할 수 있습니다. 예외 처리기가 예외를 처리하지 않는 경우 컨트롤은 미들웨어의 기본 동작 및 옵션으로 대체됩니다. 처리된 예외와 처리되지 않은 예외에 대해 다른 메트릭과 로그가 내보내집니다.

다음 예제에서는 구현을 보여줍니다.IExceptionHandler

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

다음 예제에서는 종속성 주입에 대한 구현을 등록하는 IExceptionHandler 방법을 보여줍니다.

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// Remaining Program.cs code omitted for brevity

이전 코드가 개발 환경에서 실행되는 경우:

  • CustomExceptionHandler 예외를 처리하기 위해 먼저 호출됩니다.
  • 예외를 로깅한 후 메서드가 TryHandleAsync 반환 false되므로 개발자 예외 페이지 가 표시됩니다.

다른 환경에서는 다음을 수행합니다.

  • CustomExceptionHandler 예외를 처리하기 위해 먼저 호출됩니다.
  • 예외를 로깅한 후 메서드가 TryHandleAsync 반환false되므로 페이지/Error 표시됩니다.

UseStatusCodePages

기본적으로 ASP.NET Core 앱은 ‘404 - 찾을 수 없음’과 같은 HTTP 오류 상태 코드에 대한 상태 코드 페이지를 제공하지 않습니다. 앱은 본문이 없는 HTTP 400-599 오류 상태 코드를 설정하면 상태 코드와 빈 응답 본문을 반환합니다. 일반적인 오류 상태 코드에 대해 기본 텍스트 전용 처리기를 사용하려면 Program.cs에서 UseStatusCodePages를 호출합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

요청 처리 미들웨어보다 먼저 UseStatusCodePages를 호출합니다. 예를 들어 정적 파일 미들웨어 및 엔드포인트 미들웨어보다 먼저 UseStatusCodePages를 호출합니다.

UseStatusCodePages를 사용하지 않는 경우 엔드포인트가 없는 URL로 이동하면 엔드포인트를 찾을 수 없다는 브라우저 종속 오류 메시지가 반환됩니다. UseStatusCodePages가 호출되면 브라우저에서 다음 응답을 반환합니다.

Status Code: 404; Not Found

UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

참고 항목

상태 코드 페이지 미들웨어는 예외를 catch하지 않습니다. 사용자 지정 오류 처리 페이지를 제공하려면 예외 처리기 페이지를 사용하세요.

형식 문자열을 사용하는 UseStatusCodePages

응답 콘텐츠 형식 및 텍스트를 사용자 지정하려면 콘텐츠 형식 및 형식 문자열을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

위의 코드에서 {0}은 오류 코드에 대한 자리 표시자입니다.

형식 문자열이 있는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

람다를 사용하는 UseStatusCodePages

사용자 지정 오류 처리 및 응답 쓰기 코드를 지정하려면 람다 식을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

람다를 사용하는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects 확장 메서드는:

  • ‘302 - 찾음’ 상태 코드를 클라이언트에 보냅니다.
  • URL 템플릿에 제공된 오류 처리 엔드포인트로 클라이언트를 리디렉션합니다. 오류 처리 엔드포인트는 일반적으로 오류 정보를 표시하고 HTTP 200을 반환합니다.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

위의 코드에 표시된 것처럼 URL 템플릿에는 상태 코드에 대한 {0} 자리 표시자가 포함될 수 있습니다. URL 템플릿이 ~(물결표)로 시작하는 경우 ~는 앱의 PathBase로 대체됩니다. 앱에서 엔드포인트를 지정할 때 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 일반적으로 다른 앱이 오류를 처리하는 상황에서 앱이 클라이언트를 다른 엔드포인트로 리디렉션해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 리디렉션된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 초기 리디렉션 응답과 함께 반환하면 안 되는 경우

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute 확장 메서드는:

  • 대체 경로에서 요청 파이프라인을 다시 실행하여 응답 본문을 생성합니다.
  • 파이프라인을 다시 실행하기 전이나 후에 상태 코드를 변경하지 않습니다.

새 파이프라인이 상태 코드를 완전히 제어할 수 있으므로 새 파이프라인 실행은 응답의 상태 코드를 변경할 수 있습니다. 새 파이프라인이 상태 코드를 변경하지 않으면 원래 상태 코드가 클라이언트로 전송됩니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

앱 내에 엔드포인트가 지정된 경우 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 다른 엔드포인트로 리디렉션하지 않고 요청을 처리해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 원래 요청된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 응답과 함께 반환해야 하는 경우

URL 템플릿은 /로 시작해야 하며 상태 코드에 대한 자리 표시자 {0}을 포함할 수 있습니다. 상태 코드를 쿼리 문자열 매개 변수로 전달하려면 두 번째 인수를 UseStatusCodePagesWithReExecute에 전달합니다. 예시:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

오류를 처리하는 엔드포인트는 다음 예제와 같이 오류를 생성한 원래 URL을 가져올 수 있습니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
                                    + $"{statusCodeReExecuteFeature.OriginalPath}"
                                    + $"{statusCodeReExecuteFeature.OriginalQueryString}";

        }
    }
}

이 미들웨어는 요청 파이프라인을 다시 실행할 수 있으므로 다음을 수행합니다.

  • 미들웨어는 동일한 요청으로 재진입을 처리해야 합니다. 이는 일반적으로 _next를 호출한 후 상태를 정리하거나 HttpContext에서 처리를 캐싱하여 다시 실행하지 않도록 하는 것을 의미합니다. 요청 본문을 처리할 때 폼 판독기처럼 결과를 버퍼링하거나 캐싱하는 것을 의미합니다.
  • 범위가 지정된 서비스는 동일하게 유지됩니다.

상태 코드 페이지 사용 안 함

MVC 컨트롤러 또는 작업 메서드에 대한 상태 코드 페이지를 비활성화하려면 [SkipStatusCodePages] 특성을 사용합니다.

Razor Pages 처리기 메서드 또는 MVC 컨트롤러에서 특정 요청에 대한 상태 코드 페이지를 사용하지 않으려면 IStatusCodePagesFeature를 사용합니다.

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

예외 처리 코드

예외 처리 페이지의 코드도 예외를 throw할 수 있습니다. 프로덕션 오류 페이지는 철저히 테스트하고 자체적으로 예외를 throw하지 않도록 특히 주의해야 합니다.

응답 헤더

또한 응답의 헤더가 전송되고 나면 다음 제한이 적용됩니다.

  • 앱에서 응답의 상태 코드를 변경할 수 없습니다.
  • 예외 페이지 또는 처리기를 실행할 수 없습니다. 응답을 완료하거나 연결이 중단되어야 합니다.

서버 예외 처리

앱의 예외 처리 논리 외에도 HTTP 서버 구현에서 몇 가지 예외를 처리할 수 있습니다. 응답 헤더가 전송되기 전에 예외를 catch한 서버는 응답 본문 없이 500 - Internal Server Error 응답을 보냅니다. 응답 헤더가 전송된 후에 예외를 catch한 서버는 연결을 닫습니다. 앱에서 처리되지 않는 요청은 서버에서 처리됩니다. 서버에서 요청을 처리할 때 발생하는 모든 예외는 서버의 예외 처리에 의해 처리됩니다. 앱의 사용자 지정 오류 페이지, 예외 처리 미들웨어 및 필터는 이 동작에 영향을 미치지 않습니다.

시작 예외 처리

호스팅 계층만 앱 시작 시 발생하는 예외를 처리할 수 있습니다. 시작 오류를 캡처하고 자세한 오류를 캡처하도록 호스트를 구성할 수 있습니다.

호스팅 계층은 호스트 주소/포트 바인딩 후에 오류가 발생하는 경우에만 캡처된 시작 오류에 대한 오류 페이지만 표시할 수 있습니다. 바인딩이 실패하면 결과는 다음과 같습니다.

  • 호스팅 계층에서 심각한 예외를 기록합니다.
  • dotnet 프로세스의 작동이 중단됩니다.
  • HTTP 서버가 Kestrel인 경우 오류 페이지가 표시되지 않습니다.

IIS(또는 Azure App Service) 또는 IIS Express에서 실행 중일 때, 프로세스를 시작할 수 없는 경우 ASP.NET Core 모듈이 ‘502.5 - 프로세스 실패’를 반환합니다. 자세한 내용은 Azure App Service 및 IIS에서 ASP.NET Core 문제 해결을 참조하세요.

데이터베이스 오류 페이지

데이터베이스 개발자 페이지 예외 필터 AddDatabaseDeveloperPageExceptionFilter는 Entity Framework Core 마이그레이션을 사용하여 해결할 수 있는 데이터베이스 관련 예외를 캡처합니다. 이 예외가 발생하면 문제 해결을 위해 가능한 작업의 세부 정보가 포함된 HTML 응답이 생성됩니다. 이 페이지는 개발 환경에서만 사용할 수 있습니다. 다음 코드는 데이터베이스 개발자 페이지 예외 필터를 추가합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

예외 필터

MVC 앱에서는 예외 필터를 전역으로 구성하거나 컨트롤러별 또는 작업별로 구성할 수 있습니다. Razor Pages 앱에서는 전역으로 구성하거나 페이지 모델별로 구성할 수 있습니다. 이러한 필터는 컨트롤러 작업 또는 다른 필터를 실행하는 동안 발생하는 처리되지 않은 예외를 처리합니다. 자세한 내용은 ASP.NET Core의 필터를 참조하세요.

예외 필터는 MVC 작업 내에서 발생하는 예외를 트래핑하는 데 유용하지만 기본 제공 예외 처리 미들웨어 UseExceptionHandler만큼 유연하지는 않습니다. 선택한 MVC 작업에 따라 오류 처리를 다르게 수행해야 하는 경우에만 UseExceptionHandler를 사용하는 것이 좋습니다.

모델 상태 오류

모델 상태 오류를 처리하는 방법에 대한 자세한 내용은 모델 바인딩모델 유효성 검사를 참조하세요.

문제 세부 정보

문제 세부 정보는 HTTP API 오류를 설명하는 유일한 응답 형식은 아니지만 일반적으로 HTTP API에 대한 오류를 보고하는 데 사용됩니다.

문제 세부 정보 서비스는 ASP.NET Core 문제 세부 정보 만들기를 지원하는 IProblemDetailsService 인터페이스를 구현합니다. IServiceCollectionAddProblemDetails(IServiceCollection) 확장 메서드는 기본 IProblemDetailsService 구현을 등록합니다.

ASP.NET Core 앱에서 다음 미들웨어는 Accept이 등록된 IProblemDetailsWriter(기본값: application/json)가 지원되는 콘텐츠 형식 중 하나가 포함되지 않은 HTTP 헤더를 요청하는 경우를 제외하고 AddProblemDetails가 호출될 때 문제 세부 정보 HTTP 응답을 생성합니다.

다음 코드는 아직 본문 콘텐츠가 없는 모든 HTTP 클라이언트 및 서버 오류 응답에 대한 문제 세부 정보 응답을 생성하도록 앱을 구성합니다.

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

다음 섹션에서는 문제 세부 정보 응답 본문을 사용자 지정하는 방법을 보여줍니다.

문제 세부 정보 사용자 지정

ProblemDetails의 자동 생성은 다음 옵션 중 원하는 옵션을 사용하여 사용자 지정할 수 있습니다.

  1. ProblemDetailsOptions.CustomizeProblemDetails 사용
  2. 사용자 지정 IProblemDetailsWriter 사용
  3. 미들웨어에서 IProblemDetailsService를 호출

CustomizeProblemDetails 수술

생성된 문제 세부 정보는 CustomizeProblemDetails를 사용하여 사용자 지정할 수 있으며 사용자 지정은 자동으로 생성된 모든 문제 세부 정보에 적용됩니다.

다음 코드는 ProblemDetailsOptions를 사용하여 CustomizeProblemDetails를 설정합니다.

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

예를 들어, HTTP Status 400 Bad Request 엔드포인트 결과는 다음과 같은 문제 세부 정보 응답 본문을 생성합니다.

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

사용자 지정 IProblemDetailsWriter

IProblemDetailsWriter 고급 사용자 지정을 위해 구현을 만들 수 있습니다.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

참고: 사용자 지정IProblemDetailsWriter을 사용하는 경우 호출AddMvcAddControllersWithViewsAddControllersAddRazorPages하기 전에 사용자 지정 IProblemDetailsWriter 을 등록해야 합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

미들웨어의 문제 세부 정보

ProblemDetailsOptionsCustomizeProblemDetails와 함께 사용하는 다른 방법은 미들웨어에서 ProblemDetails를 설정하는 것입니다. IProblemDetailsService.WriteAsync를 호출하여 문제 세부 정보 응답을 작성할 수 있습니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

앞의 코드에서 최소 API 엔드포인트 /divide/squareroot는 오류 입력에 대해 예상되는 사용자 지정 문제 응답을 반환합니다.

API 컨트롤러 엔드포인트는 사용자 지정 문제 응답이 아니라 오류 입력에 대한 기본 문제 응답을 반환합니다. API 컨트롤러가 응답 스트림에 기록되었기 때문에 기본 문제 응답이 반환됩니다. 오류 상태 코드에 대한 문제 세부 정보는 IProblemDetailsService.WriteAsync가 호출되고 응답이 다시 작성되지 않습니다.

다음 ValuesControllerBadRequestResult를 반환하며, 이는 응답 스트림에 쓰고 사용자 지정 문제 응답이 반환되지 않도록 합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

다음은 Values3ControllerControllerBase.Problem를 반환하여 예상되는 사용자 지정 문제 결과가 반환되도록 합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

예외에 대한 ProblemDetails 페이로드 생성

다음 앱을 고려합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

비개발 환경에서 예외가 발생하면 클라이언트에 반환되는 표준 ProblemDetails 응답은 다음과 같습니다.

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

대부분의 앱에서 위의 코드는 예외에 필요한 모든 코드입니다. 그러나 다음 섹션에서는 더 자세한 문제 응답을 가져오는 방법을 보여줍니다.

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 오류에 액세스하고 IProblemDetailsService.WriteAsync를 사용하여 문제 세부 정보 응답을 작성할 수 있습니다.

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

문제 세부 정보를 생성하는 다른 방법은 예외 및 클라이언트 오류를 문제 세부 정보에 매핑하는 데 사용할 수 있는 타사 NuGet 패키지 Hellang.Middleware.ProblemDetails를 사용하는 것입니다.

추가 리소스

작성자: Tom Dykstra

이 항목에서는 ASP.NET Core 웹앱에서 오류를 처리하는 일반적인 접근법을 다룹니다. ASP.NET Core 컨트롤러 기반 웹 API의 오류 처리 및 최소 API의 오류 처리도 참조하세요.

개발자 예외 페이지

개발자 예외 페이지에는 처리되지 않은 요청 예외에 대한 자세한 정보가 표시됩니다. ASP.NET Core 앱은 다음과 같은 경우 기본적으로 개발자 예외 페이지를 사용할 수 있습니다.

개발자 예외 페이지는 미들웨어 파이프라인의 앞부분에 실행되므로, 다음에 오는 미들웨어에서 throw된 미처리 예외를 catch할 수 있습니다.

앱이 프로덕션 환경에서 실행되는 경우에는 자세한 예외 정보를 공개적으로 표시해서는 안 됩니다. 환경을 구성하는 방법에 대한 자세한 내용은 ASP.NET Core에서 여러 환경 사용을 참조하세요.

개발자 예외 페이지에는 예외 및 요청에 대한 다음 정보가 포함될 수 있습니다.

  • 스택 추적
  • 쿼리 문자열 매개 변수(있는 경우)
  • 쿠키(있는 경우)
  • 헤더

개발자 예외 페이지는 어떠한 정보 제공도 보장하지 않습니다. 전체 오류 정보를 보려면 로깅을 사용하세요.

예외 처리기 페이지

프로덕션 환경의 사용자 지정 오류 처리 페이지를 구성하려면 UseExceptionHandler를 호출합니다. 이 예외 처리 미들웨어에서 다음을 수행합니다.

  • 미처리 예외를 catch하고 기록합니다.
  • 대체 파이프라인에서 표시된 경로를 사용하여 요청을 다시 실행합니다. 응답이 시작된 경우에는 요청이 다시 실행되지 않습니다. 템플릿 생성 코드는 /Error 경로를 사용하여 요청을 다시 실행합니다.

Warning

대체 파이프라인이 자체 예외를 throw하는 경우 예외 처리 미들웨어가 원래 예외를 다시 throw합니다.

이 미들웨어는 요청 파이프라인을 다시 실행할 수 있으므로 다음을 수행합니다.

  • 미들웨어는 동일한 요청으로 재진입을 처리해야 합니다. 이는 일반적으로 _next를 호출한 후 상태를 정리하거나 HttpContext에서 처리를 캐싱하여 다시 실행하지 않도록 하는 것을 의미합니다. 요청 본문을 처리할 때 폼 판독기처럼 결과를 버퍼링하거나 캐싱하는 것을 의미합니다.
  • 템플릿에 사용되는 UseExceptionHandler(IApplicationBuilder, String) 오버로드의 경우 요청 경로만 수정되고 경로 데이터가 지워집니다. 헤더, 메서드 및 항목과 같은 요청 데이터는 모두 있는 그대로 재사용됩니다.
  • 범위가 지정된 서비스는 동일하게 유지됩니다.

다음 예제에서 UseExceptionHandler는 개발 환경이 아닌 환경에서 예외 처리 미들웨어를 추가합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Razor Pages 앱 템플릿은 Pages 폴더에 오류 페이지(.cshtmlPageModel클래스ErrorModel)를 제공합니다. MVC 앱의 프로젝트 템플릿에는 Home 컨트롤러에 대한 Error 작업 메서드와 오류 보기가 포함됩니다.

예외 처리 미들웨어는 원래 HTTP 메서드를 사용하여 요청을 다시 실행합니다. 오류 처리기 엔드포인트가 특정 HTTP 메서드 집합으로 제한되는 경우 해당 HTTP 메서드에 대해서만 실행됩니다. 예를 들어 [HttpGet] 특성을 사용하는 MVC 컨트롤러 작업은 GET 요청에 대해서만 실행됩니다. 모든 요청이 사용자 지정 오류 처리 페이지에 도달하도록 하려면 특정 HTTP 메서드 집합으로 제한하지 마세요.

원래 HTTP 메서드에 따라 예외를 서로 다르게 처리하려면 다음을 수행합니다.

  • Razor Pages의 경우 여러 처리기 메서드를 만듭니다. 예를 들어 OnGet을 사용하여 GET 예외를 처리하고 OnPost를 사용하여 POST 예외를 처리합니다.
  • MVC의 경우 여러 작업에 HTTP 동사 특성을 적용합니다. 예를 들어 [HttpGet]을 사용하여 GET 예외를 처리하고 [HttpPost]를 사용하여 POST 예외를 처리합니다.

인증되지 않은 사용자가 사용자 지정 오류 처리 페이지를 볼 수 있도록 하려면 익명 액세스를 지원해야 합니다.

예외에 액세스

IExceptionHandlerPathFeature를 사용하여 오류 처리기에서 예외 및 원래 요청 경로에 액세스합니다. 다음 예제에서는 IExceptionHandlerPathFeature를 사용하여 throw된 예외에 대한 자세한 정보를 가져옵니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

예외 처리기 람다

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 응답을 반환하기 전에 오류에 액세스할 수 있습니다.

다음 코드에서는 예외 처리에 람다를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

IExceptionHandler

IExceptionHandler 는 개발자에게 중앙 위치에서 알려진 예외를 처리하기 위한 콜백을 제공하는 인터페이스입니다.

IExceptionHandler 구현은 호출 IServiceCollection.AddExceptionHandler<T>하여 등록됩니다. 인스턴스의 수 IExceptionHandler 명은 싱글톤입니다. 여러 구현을 추가할 수 있으며 등록된 순서대로 호출됩니다.

예외 처리기가 요청을 처리하는 경우 처리를 중지하도록 반환 true 할 수 있습니다. 예외 처리기가 예외를 처리하지 않는 경우 컨트롤은 미들웨어의 기본 동작 및 옵션으로 대체됩니다. 처리된 예외와 처리되지 않은 예외에 대해 다른 메트릭과 로그가 내보내집니다.

다음 예제에서는 구현을 보여줍니다.IExceptionHandler

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

다음 예제에서는 종속성 주입에 대한 구현을 등록하는 IExceptionHandler 방법을 보여줍니다.

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// Remaining Program.cs code omitted for brevity

이전 코드가 개발 환경에서 실행되는 경우:

  • CustomExceptionHandler 예외를 처리하기 위해 먼저 호출됩니다.
  • 예외를 로깅한 후 메서드가 TryHandleException 반환 false되므로 개발자 예외 페이지 가 표시됩니다.

다른 환경에서는 다음을 수행합니다.

  • CustomExceptionHandler 예외를 처리하기 위해 먼저 호출됩니다.
  • 예외를 로깅한 후 메서드가 TryHandleException 반환false되므로 페이지/Error 표시됩니다.

UseStatusCodePages

기본적으로 ASP.NET Core 앱은 ‘404 - 찾을 수 없음’과 같은 HTTP 오류 상태 코드에 대한 상태 코드 페이지를 제공하지 않습니다. 앱은 본문이 없는 HTTP 400-599 오류 상태 코드를 설정하면 상태 코드와 빈 응답 본문을 반환합니다. 일반적인 오류 상태 코드에 대해 기본 텍스트 전용 처리기를 사용하려면 Program.cs에서 UseStatusCodePages를 호출합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

요청 처리 미들웨어보다 먼저 UseStatusCodePages를 호출합니다. 예를 들어 정적 파일 미들웨어 및 엔드포인트 미들웨어보다 먼저 UseStatusCodePages를 호출합니다.

UseStatusCodePages를 사용하지 않는 경우 엔드포인트가 없는 URL로 이동하면 엔드포인트를 찾을 수 없다는 브라우저 종속 오류 메시지가 반환됩니다. UseStatusCodePages가 호출되면 브라우저에서 다음 응답을 반환합니다.

Status Code: 404; Not Found

UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

참고 항목

상태 코드 페이지 미들웨어는 예외를 catch하지 않습니다. 사용자 지정 오류 처리 페이지를 제공하려면 예외 처리기 페이지를 사용하세요.

형식 문자열을 사용하는 UseStatusCodePages

응답 콘텐츠 형식 및 텍스트를 사용자 지정하려면 콘텐츠 형식 및 형식 문자열을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

위의 코드에서 {0}은 오류 코드에 대한 자리 표시자입니다.

형식 문자열이 있는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

람다를 사용하는 UseStatusCodePages

사용자 지정 오류 처리 및 응답 쓰기 코드를 지정하려면 람다 식을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

람다를 사용하는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects 확장 메서드는:

  • ‘302 - 찾음’ 상태 코드를 클라이언트에 보냅니다.
  • URL 템플릿에 제공된 오류 처리 엔드포인트로 클라이언트를 리디렉션합니다. 오류 처리 엔드포인트는 일반적으로 오류 정보를 표시하고 HTTP 200을 반환합니다.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

위의 코드에 표시된 것처럼 URL 템플릿에는 상태 코드에 대한 {0} 자리 표시자가 포함될 수 있습니다. URL 템플릿이 ~(물결표)로 시작하는 경우 ~는 앱의 PathBase로 대체됩니다. 앱에서 엔드포인트를 지정할 때 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 일반적으로 다른 앱이 오류를 처리하는 상황에서 앱이 클라이언트를 다른 엔드포인트로 리디렉션해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 리디렉션된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 초기 리디렉션 응답과 함께 반환하면 안 되는 경우

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute 확장 메서드는:

  • 대체 경로에서 요청 파이프라인을 다시 실행하여 응답 본문을 생성합니다.
  • 파이프라인을 다시 실행하기 전이나 후에 상태 코드를 변경하지 않습니다.

새 파이프라인이 상태 코드를 완전히 제어할 수 있으므로 새 파이프라인 실행은 응답의 상태 코드를 변경할 수 있습니다. 새 파이프라인이 상태 코드를 변경하지 않으면 원래 상태 코드가 클라이언트로 전송됩니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

앱 내에 엔드포인트가 지정된 경우 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 다른 엔드포인트로 리디렉션하지 않고 요청을 처리해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 원래 요청된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 응답과 함께 반환해야 하는 경우

URL 템플릿은 /로 시작해야 하며 상태 코드에 대한 자리 표시자 {0}을 포함할 수 있습니다. 상태 코드를 쿼리 문자열 매개 변수로 전달하려면 두 번째 인수를 UseStatusCodePagesWithReExecute에 전달합니다. 예시:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

오류를 처리하는 엔드포인트는 다음 예제와 같이 오류를 생성한 원래 URL을 가져올 수 있습니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
                                    + $"{statusCodeReExecuteFeature.OriginalPath}"
                                    + $"{statusCodeReExecuteFeature.OriginalQueryString}";

        }
    }
}

이 미들웨어는 요청 파이프라인을 다시 실행할 수 있으므로 다음을 수행합니다.

  • 미들웨어는 동일한 요청으로 재진입을 처리해야 합니다. 이는 일반적으로 _next를 호출한 후 상태를 정리하거나 HttpContext에서 처리를 캐싱하여 다시 실행하지 않도록 하는 것을 의미합니다. 요청 본문을 처리할 때 폼 판독기처럼 결과를 버퍼링하거나 캐싱하는 것을 의미합니다.
  • 범위가 지정된 서비스는 동일하게 유지됩니다.

상태 코드 페이지 사용 안 함

MVC 컨트롤러 또는 작업 메서드에 대한 상태 코드 페이지를 비활성화하려면 [SkipStatusCodePages] 특성을 사용합니다.

Razor Pages 처리기 메서드 또는 MVC 컨트롤러에서 특정 요청에 대한 상태 코드 페이지를 사용하지 않으려면 IStatusCodePagesFeature를 사용합니다.

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

예외 처리 코드

예외 처리 페이지의 코드도 예외를 throw할 수 있습니다. 프로덕션 오류 페이지는 철저히 테스트하고 자체적으로 예외를 throw하지 않도록 특히 주의해야 합니다.

응답 헤더

또한 응답의 헤더가 전송되고 나면 다음 제한이 적용됩니다.

  • 앱에서 응답의 상태 코드를 변경할 수 없습니다.
  • 예외 페이지 또는 처리기를 실행할 수 없습니다. 응답을 완료하거나 연결이 중단되어야 합니다.

서버 예외 처리

앱의 예외 처리 논리 외에도 HTTP 서버 구현에서 몇 가지 예외를 처리할 수 있습니다. 응답 헤더가 전송되기 전에 예외를 catch한 서버는 응답 본문 없이 500 - Internal Server Error 응답을 보냅니다. 응답 헤더가 전송된 후에 예외를 catch한 서버는 연결을 닫습니다. 앱에서 처리되지 않는 요청은 서버에서 처리됩니다. 서버에서 요청을 처리할 때 발생하는 모든 예외는 서버의 예외 처리에 의해 처리됩니다. 앱의 사용자 지정 오류 페이지, 예외 처리 미들웨어 및 필터는 이 동작에 영향을 미치지 않습니다.

시작 예외 처리

호스팅 계층만 앱 시작 시 발생하는 예외를 처리할 수 있습니다. 시작 오류를 캡처하고 자세한 오류를 캡처하도록 호스트를 구성할 수 있습니다.

호스팅 계층은 호스트 주소/포트 바인딩 후에 오류가 발생하는 경우에만 캡처된 시작 오류에 대한 오류 페이지만 표시할 수 있습니다. 바인딩이 실패하면 결과는 다음과 같습니다.

  • 호스팅 계층에서 심각한 예외를 기록합니다.
  • dotnet 프로세스의 작동이 중단됩니다.
  • HTTP 서버가 Kestrel인 경우 오류 페이지가 표시되지 않습니다.

IIS(또는 Azure App Service) 또는 IIS Express에서 실행 중일 때, 프로세스를 시작할 수 없는 경우 ASP.NET Core 모듈이 ‘502.5 - 프로세스 실패’를 반환합니다. 자세한 내용은 Azure App Service 및 IIS에서 ASP.NET Core 문제 해결을 참조하세요.

데이터베이스 오류 페이지

데이터베이스 개발자 페이지 예외 필터 AddDatabaseDeveloperPageExceptionFilter는 Entity Framework Core 마이그레이션을 사용하여 해결할 수 있는 데이터베이스 관련 예외를 캡처합니다. 이 예외가 발생하면 문제 해결을 위해 가능한 작업의 세부 정보가 포함된 HTML 응답이 생성됩니다. 이 페이지는 개발 환경에서만 사용할 수 있습니다. 다음 코드는 데이터베이스 개발자 페이지 예외 필터를 추가합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

예외 필터

MVC 앱에서는 예외 필터를 전역으로 구성하거나 컨트롤러별 또는 작업별로 구성할 수 있습니다. Razor Pages 앱에서는 전역으로 구성하거나 페이지 모델별로 구성할 수 있습니다. 이러한 필터는 컨트롤러 작업 또는 다른 필터를 실행하는 동안 발생하는 처리되지 않은 예외를 처리합니다. 자세한 내용은 ASP.NET Core의 필터를 참조하세요.

예외 필터는 MVC 작업 내에서 발생하는 예외를 트래핑하는 데 유용하지만 기본 제공 예외 처리 미들웨어 UseExceptionHandler만큼 유연하지는 않습니다. 선택한 MVC 작업에 따라 오류 처리를 다르게 수행해야 하는 경우에만 UseExceptionHandler를 사용하는 것이 좋습니다.

모델 상태 오류

모델 상태 오류를 처리하는 방법에 대한 자세한 내용은 모델 바인딩모델 유효성 검사를 참조하세요.

문제 세부 정보

문제 세부 정보는 HTTP API 오류를 설명하는 유일한 응답 형식은 아니지만 일반적으로 HTTP API에 대한 오류를 보고하는 데 사용됩니다.

문제 세부 정보 서비스는 ASP.NET Core 문제 세부 정보 만들기를 지원하는 IProblemDetailsService 인터페이스를 구현합니다. IServiceCollectionAddProblemDetails(IServiceCollection) 확장 메서드는 기본 IProblemDetailsService 구현을 등록합니다.

ASP.NET Core 앱에서 다음 미들웨어는 Accept이 등록된 IProblemDetailsWriter(기본값: application/json)가 지원되는 콘텐츠 형식 중 하나가 포함되지 않은 HTTP 헤더를 요청하는 경우를 제외하고 AddProblemDetails가 호출될 때 문제 세부 정보 HTTP 응답을 생성합니다.

다음 코드는 아직 본문 콘텐츠가 없는 모든 HTTP 클라이언트 및 서버 오류 응답에 대한 문제 세부 정보 응답을 생성하도록 앱을 구성합니다.

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

다음 섹션에서는 문제 세부 정보 응답 본문을 사용자 지정하는 방법을 보여줍니다.

문제 세부 정보 사용자 지정

ProblemDetails의 자동 생성은 다음 옵션 중 원하는 옵션을 사용하여 사용자 지정할 수 있습니다.

  1. ProblemDetailsOptions.CustomizeProblemDetails 사용
  2. 사용자 지정 IProblemDetailsWriter 사용
  3. 미들웨어에서 IProblemDetailsService를 호출

CustomizeProblemDetails 수술

생성된 문제 세부 정보는 CustomizeProblemDetails를 사용하여 사용자 지정할 수 있으며 사용자 지정은 자동으로 생성된 모든 문제 세부 정보에 적용됩니다.

다음 코드는 ProblemDetailsOptions를 사용하여 CustomizeProblemDetails를 설정합니다.

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

예를 들어, HTTP Status 400 Bad Request 엔드포인트 결과는 다음과 같은 문제 세부 정보 응답 본문을 생성합니다.

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

사용자 지정 IProblemDetailsWriter

IProblemDetailsWriter 고급 사용자 지정을 위해 구현을 만들 수 있습니다.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

참고: 사용자 지정IProblemDetailsWriter을 사용하는 경우 호출AddMvcAddControllersWithViewsAddControllersAddRazorPages하기 전에 사용자 지정 IProblemDetailsWriter 을 등록해야 합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

미들웨어의 문제 세부 정보

ProblemDetailsOptionsCustomizeProblemDetails와 함께 사용하는 다른 방법은 미들웨어에서 ProblemDetails를 설정하는 것입니다. IProblemDetailsService.WriteAsync를 호출하여 문제 세부 정보 응답을 작성할 수 있습니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

앞의 코드에서 최소 API 엔드포인트 /divide/squareroot는 오류 입력에 대해 예상되는 사용자 지정 문제 응답을 반환합니다.

API 컨트롤러 엔드포인트는 사용자 지정 문제 응답이 아니라 오류 입력에 대한 기본 문제 응답을 반환합니다. API 컨트롤러가 응답 스트림에 기록되었기 때문에 기본 문제 응답이 반환됩니다. 오류 상태 코드에 대한 문제 세부 정보는 IProblemDetailsService.WriteAsync가 호출되고 응답이 다시 작성되지 않습니다.

다음 ValuesControllerBadRequestResult를 반환하며, 이는 응답 스트림에 쓰고 사용자 지정 문제 응답이 반환되지 않도록 합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

다음은 Values3ControllerControllerBase.Problem를 반환하여 예상되는 사용자 지정 문제 결과가 반환되도록 합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

예외에 대한 ProblemDetails 페이로드 생성

다음 앱을 고려합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

비개발 환경에서 예외가 발생하면 클라이언트에 반환되는 표준 ProblemDetails 응답은 다음과 같습니다.

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

대부분의 앱에서 위의 코드는 예외에 필요한 모든 코드입니다. 그러나 다음 섹션에서는 더 자세한 문제 응답을 가져오는 방법을 보여줍니다.

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 오류에 액세스하고 IProblemDetailsService.WriteAsync를 사용하여 문제 세부 정보 응답을 작성할 수 있습니다.

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

문제 세부 정보를 생성하는 다른 방법은 예외 및 클라이언트 오류를 문제 세부 정보에 매핑하는 데 사용할 수 있는 타사 NuGet 패키지 Hellang.Middleware.ProblemDetails를 사용하는 것입니다.

추가 리소스

작성자: Tom Dykstra

이 항목에서는 ASP.NET Core 웹앱에서 오류를 처리하는 일반적인 접근법을 다룹니다. ASP.NET Core 컨트롤러 기반 웹 API의 오류 처리 및 최소 API의 오류 처리도 참조하세요.

개발자 예외 페이지

개발자 예외 페이지에는 처리되지 않은 요청 예외에 대한 자세한 정보가 표시됩니다. ASP.NET Core 앱은 다음과 같은 경우 기본적으로 개발자 예외 페이지를 사용할 수 있습니다.

개발자 예외 페이지는 미들웨어 파이프라인의 앞부분에 실행되므로, 다음에 오는 미들웨어에서 throw된 미처리 예외를 catch할 수 있습니다.

앱이 프로덕션 환경에서 실행되는 경우에는 자세한 예외 정보를 공개적으로 표시해서는 안 됩니다. 환경을 구성하는 방법에 대한 자세한 내용은 ASP.NET Core에서 여러 환경 사용을 참조하세요.

개발자 예외 페이지에는 예외 및 요청에 대한 다음 정보가 포함될 수 있습니다.

  • 스택 추적
  • 쿼리 문자열 매개 변수(있는 경우)
  • 쿠키(있는 경우)
  • 헤더

개발자 예외 페이지는 어떠한 정보 제공도 보장하지 않습니다. 전체 오류 정보를 보려면 로깅을 사용하세요.

예외 처리기 페이지

프로덕션 환경의 사용자 지정 오류 처리 페이지를 구성하려면 UseExceptionHandler를 호출합니다. 이 예외 처리 미들웨어에서 다음을 수행합니다.

  • 미처리 예외를 catch하고 기록합니다.
  • 대체 파이프라인에서 표시된 경로를 사용하여 요청을 다시 실행합니다. 응답이 시작된 경우에는 요청이 다시 실행되지 않습니다. 템플릿 생성 코드는 /Error 경로를 사용하여 요청을 다시 실행합니다.

Warning

대체 파이프라인이 자체 예외를 throw하는 경우 예외 처리 미들웨어가 원래 예외를 다시 throw합니다.

이 미들웨어는 요청 파이프라인을 다시 실행할 수 있으므로 다음을 수행합니다.

  • 미들웨어는 동일한 요청으로 재진입을 처리해야 합니다. 이는 일반적으로 _next를 호출한 후 상태를 정리하거나 HttpContext에서 처리를 캐싱하여 다시 실행하지 않도록 하는 것을 의미합니다. 요청 본문을 처리할 때 폼 판독기처럼 결과를 버퍼링하거나 캐싱하는 것을 의미합니다.
  • 템플릿에 사용되는 UseExceptionHandler(IApplicationBuilder, String) 오버로드의 경우 요청 경로만 수정되고 경로 데이터가 지워집니다. 헤더, 메서드 및 항목과 같은 요청 데이터는 모두 있는 그대로 재사용됩니다.
  • 범위가 지정된 서비스는 동일하게 유지됩니다.

다음 예제에서 UseExceptionHandler는 개발 환경이 아닌 환경에서 예외 처리 미들웨어를 추가합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Razor Pages 앱 템플릿은 Pages 폴더에 오류 페이지(.cshtmlPageModel클래스ErrorModel)를 제공합니다. MVC 앱의 프로젝트 템플릿에는 Home 컨트롤러에 대한 Error 작업 메서드와 오류 보기가 포함됩니다.

예외 처리 미들웨어는 원래 HTTP 메서드를 사용하여 요청을 다시 실행합니다. 오류 처리기 엔드포인트가 특정 HTTP 메서드 집합으로 제한되는 경우 해당 HTTP 메서드에 대해서만 실행됩니다. 예를 들어 [HttpGet] 특성을 사용하는 MVC 컨트롤러 작업은 GET 요청에 대해서만 실행됩니다. 모든 요청이 사용자 지정 오류 처리 페이지에 도달하도록 하려면 특정 HTTP 메서드 집합으로 제한하지 마세요.

원래 HTTP 메서드에 따라 예외를 서로 다르게 처리하려면 다음을 수행합니다.

  • Razor Pages의 경우 여러 처리기 메서드를 만듭니다. 예를 들어 OnGet을 사용하여 GET 예외를 처리하고 OnPost를 사용하여 POST 예외를 처리합니다.
  • MVC의 경우 여러 작업에 HTTP 동사 특성을 적용합니다. 예를 들어 [HttpGet]을 사용하여 GET 예외를 처리하고 [HttpPost]를 사용하여 POST 예외를 처리합니다.

인증되지 않은 사용자가 사용자 지정 오류 처리 페이지를 볼 수 있도록 하려면 익명 액세스를 지원해야 합니다.

예외에 액세스

IExceptionHandlerPathFeature를 사용하여 오류 처리기에서 예외 및 원래 요청 경로에 액세스합니다. 다음 예제에서는 IExceptionHandlerPathFeature를 사용하여 throw된 예외에 대한 자세한 정보를 가져옵니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

예외 처리기 람다

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 응답을 반환하기 전에 오류에 액세스할 수 있습니다.

다음 코드에서는 예외 처리에 람다를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

UseStatusCodePages

기본적으로 ASP.NET Core 앱은 ‘404 - 찾을 수 없음’과 같은 HTTP 오류 상태 코드에 대한 상태 코드 페이지를 제공하지 않습니다. 앱은 본문이 없는 HTTP 400-599 오류 상태 코드를 설정하면 상태 코드와 빈 응답 본문을 반환합니다. 일반적인 오류 상태 코드에 대해 기본 텍스트 전용 처리기를 사용하려면 Program.cs에서 UseStatusCodePages를 호출합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

요청 처리 미들웨어보다 먼저 UseStatusCodePages를 호출합니다. 예를 들어 정적 파일 미들웨어 및 엔드포인트 미들웨어보다 먼저 UseStatusCodePages를 호출합니다.

UseStatusCodePages를 사용하지 않는 경우 엔드포인트가 없는 URL로 이동하면 엔드포인트를 찾을 수 없다는 브라우저 종속 오류 메시지가 반환됩니다. UseStatusCodePages가 호출되면 브라우저에서 다음 응답을 반환합니다.

Status Code: 404; Not Found

UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

참고 항목

상태 코드 페이지 미들웨어는 예외를 catch하지 않습니다. 사용자 지정 오류 처리 페이지를 제공하려면 예외 처리기 페이지를 사용하세요.

형식 문자열을 사용하는 UseStatusCodePages

응답 콘텐츠 형식 및 텍스트를 사용자 지정하려면 콘텐츠 형식 및 형식 문자열을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

위의 코드에서 {0}은 오류 코드에 대한 자리 표시자입니다.

형식 문자열이 있는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

람다를 사용하는 UseStatusCodePages

사용자 지정 오류 처리 및 응답 쓰기 코드를 지정하려면 람다 식을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

람다를 사용하는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects 확장 메서드는:

  • ‘302 - 찾음’ 상태 코드를 클라이언트에 보냅니다.
  • URL 템플릿에 제공된 오류 처리 엔드포인트로 클라이언트를 리디렉션합니다. 오류 처리 엔드포인트는 일반적으로 오류 정보를 표시하고 HTTP 200을 반환합니다.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

위의 코드에 표시된 것처럼 URL 템플릿에는 상태 코드에 대한 {0} 자리 표시자가 포함될 수 있습니다. URL 템플릿이 ~(물결표)로 시작하는 경우 ~는 앱의 PathBase로 대체됩니다. 앱에서 엔드포인트를 지정할 때 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 일반적으로 다른 앱이 오류를 처리하는 상황에서 앱이 클라이언트를 다른 엔드포인트로 리디렉션해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 리디렉션된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 초기 리디렉션 응답과 함께 반환하면 안 되는 경우

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute 확장 메서드는:

  • 대체 경로에서 요청 파이프라인을 다시 실행하여 응답 본문을 생성합니다.
  • 파이프라인을 다시 실행하기 전이나 후에 상태 코드를 변경하지 않습니다.

새 파이프라인이 상태 코드를 완전히 제어할 수 있으므로 새 파이프라인 실행은 응답의 상태 코드를 변경할 수 있습니다. 새 파이프라인이 상태 코드를 변경하지 않으면 원래 상태 코드가 클라이언트로 전송됩니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

앱 내에 엔드포인트가 지정된 경우 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 다른 엔드포인트로 리디렉션하지 않고 요청을 처리해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 원래 요청된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 응답과 함께 반환해야 하는 경우

URL 템플릿은 /로 시작해야 하며 상태 코드에 대한 자리 표시자 {0}을 포함할 수 있습니다. 상태 코드를 쿼리 문자열 매개 변수로 전달하려면 두 번째 인수를 UseStatusCodePagesWithReExecute에 전달합니다. 예시:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

오류를 처리하는 엔드포인트는 다음 예제와 같이 오류를 생성한 원래 URL을 가져올 수 있습니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
                                    + $"{statusCodeReExecuteFeature.OriginalPath}"
                                    + $"{statusCodeReExecuteFeature.OriginalQueryString}";

        }
    }
}

이 미들웨어는 요청 파이프라인을 다시 실행할 수 있으므로 다음을 수행합니다.

  • 미들웨어는 동일한 요청으로 재진입을 처리해야 합니다. 이는 일반적으로 _next를 호출한 후 상태를 정리하거나 HttpContext에서 처리를 캐싱하여 다시 실행하지 않도록 하는 것을 의미합니다. 요청 본문을 처리할 때 폼 판독기처럼 결과를 버퍼링하거나 캐싱하는 것을 의미합니다.
  • 범위가 지정된 서비스는 동일하게 유지됩니다.

상태 코드 페이지 사용 안 함

MVC 컨트롤러 또는 작업 메서드에 대한 상태 코드 페이지를 비활성화하려면 [SkipStatusCodePages] 특성을 사용합니다.

Razor Pages 처리기 메서드 또는 MVC 컨트롤러에서 특정 요청에 대한 상태 코드 페이지를 사용하지 않으려면 IStatusCodePagesFeature를 사용합니다.

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

예외 처리 코드

예외 처리 페이지의 코드도 예외를 throw할 수 있습니다. 프로덕션 오류 페이지는 철저히 테스트하고 자체적으로 예외를 throw하지 않도록 특히 주의해야 합니다.

응답 헤더

또한 응답의 헤더가 전송되고 나면 다음 제한이 적용됩니다.

  • 앱에서 응답의 상태 코드를 변경할 수 없습니다.
  • 예외 페이지 또는 처리기를 실행할 수 없습니다. 응답을 완료하거나 연결이 중단되어야 합니다.

서버 예외 처리

앱의 예외 처리 논리 외에도 HTTP 서버 구현에서 몇 가지 예외를 처리할 수 있습니다. 응답 헤더가 전송되기 전에 예외를 catch한 서버는 응답 본문 없이 500 - Internal Server Error 응답을 보냅니다. 응답 헤더가 전송된 후에 예외를 catch한 서버는 연결을 닫습니다. 앱에서 처리되지 않는 요청은 서버에서 처리됩니다. 서버에서 요청을 처리할 때 발생하는 모든 예외는 서버의 예외 처리에 의해 처리됩니다. 앱의 사용자 지정 오류 페이지, 예외 처리 미들웨어 및 필터는 이 동작에 영향을 미치지 않습니다.

시작 예외 처리

호스팅 계층만 앱 시작 시 발생하는 예외를 처리할 수 있습니다. 시작 오류를 캡처하고 자세한 오류를 캡처하도록 호스트를 구성할 수 있습니다.

호스팅 계층은 호스트 주소/포트 바인딩 후에 오류가 발생하는 경우에만 캡처된 시작 오류에 대한 오류 페이지만 표시할 수 있습니다. 바인딩이 실패하면 결과는 다음과 같습니다.

  • 호스팅 계층에서 심각한 예외를 기록합니다.
  • dotnet 프로세스의 작동이 중단됩니다.
  • HTTP 서버가 Kestrel인 경우 오류 페이지가 표시되지 않습니다.

IIS(또는 Azure App Service) 또는 IIS Express에서 실행 중일 때, 프로세스를 시작할 수 없는 경우 ASP.NET Core 모듈이 ‘502.5 - 프로세스 실패’를 반환합니다. 자세한 내용은 Azure App Service 및 IIS에서 ASP.NET Core 문제 해결을 참조하세요.

데이터베이스 오류 페이지

데이터베이스 개발자 페이지 예외 필터 AddDatabaseDeveloperPageExceptionFilter는 Entity Framework Core 마이그레이션을 사용하여 해결할 수 있는 데이터베이스 관련 예외를 캡처합니다. 이 예외가 발생하면 문제 해결을 위해 가능한 작업의 세부 정보가 포함된 HTML 응답이 생성됩니다. 이 페이지는 개발 환경에서만 사용할 수 있습니다. 다음 코드는 데이터베이스 개발자 페이지 예외 필터를 추가합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

예외 필터

MVC 앱에서는 예외 필터를 전역으로 구성하거나 컨트롤러별 또는 작업별로 구성할 수 있습니다. Razor Pages 앱에서는 전역으로 구성하거나 페이지 모델별로 구성할 수 있습니다. 이러한 필터는 컨트롤러 작업 또는 다른 필터를 실행하는 동안 발생하는 처리되지 않은 예외를 처리합니다. 자세한 내용은 ASP.NET Core의 필터를 참조하세요.

예외 필터는 MVC 작업 내에서 발생하는 예외를 트래핑하는 데 유용하지만 기본 제공 예외 처리 미들웨어 UseExceptionHandler만큼 유연하지는 않습니다. 선택한 MVC 작업에 따라 오류 처리를 다르게 수행해야 하는 경우에만 UseExceptionHandler를 사용하는 것이 좋습니다.

모델 상태 오류

모델 상태 오류를 처리하는 방법에 대한 자세한 내용은 모델 바인딩모델 유효성 검사를 참조하세요.

문제 세부 정보

문제 세부 정보는 HTTP API 오류를 설명하는 유일한 응답 형식은 아니지만 일반적으로 HTTP API에 대한 오류를 보고하는 데 사용됩니다.

문제 세부 정보 서비스는 ASP.NET Core 문제 세부 정보 만들기를 지원하는 IProblemDetailsService 인터페이스를 구현합니다. IServiceCollectionAddProblemDetails(IServiceCollection) 확장 메서드는 기본 IProblemDetailsService 구현을 등록합니다.

ASP.NET Core 앱에서 다음 미들웨어는 Accept이 등록된 IProblemDetailsWriter(기본값: application/json)가 지원되는 콘텐츠 형식 중 하나가 포함되지 않은 HTTP 헤더를 요청하는 경우를 제외하고 AddProblemDetails가 호출될 때 문제 세부 정보 HTTP 응답을 생성합니다.

다음 코드는 아직 본문 콘텐츠가 없는 모든 HTTP 클라이언트 및 서버 오류 응답에 대한 문제 세부 정보 응답을 생성하도록 앱을 구성합니다.

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

다음 섹션에서는 문제 세부 정보 응답 본문을 사용자 지정하는 방법을 보여줍니다.

문제 세부 정보 사용자 지정

ProblemDetails의 자동 생성은 다음 옵션 중 원하는 옵션을 사용하여 사용자 지정할 수 있습니다.

  1. ProblemDetailsOptions.CustomizeProblemDetails 사용
  2. 사용자 지정 IProblemDetailsWriter 사용
  3. 미들웨어에서 IProblemDetailsService를 호출

CustomizeProblemDetails 수술

생성된 문제 세부 정보는 CustomizeProblemDetails를 사용하여 사용자 지정할 수 있으며 사용자 지정은 자동으로 생성된 모든 문제 세부 정보에 적용됩니다.

다음 코드는 ProblemDetailsOptions를 사용하여 CustomizeProblemDetails를 설정합니다.

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

예를 들어, HTTP Status 400 Bad Request 엔드포인트 결과는 다음과 같은 문제 세부 정보 응답 본문을 생성합니다.

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

사용자 지정 IProblemDetailsWriter

IProblemDetailsWriter 고급 사용자 지정을 위해 구현을 만들 수 있습니다.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

참고: 사용자 지정IProblemDetailsWriter을 사용하는 경우 호출AddMvcAddControllersWithViewsAddControllersAddRazorPages하기 전에 사용자 지정 IProblemDetailsWriter 을 등록해야 합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

미들웨어의 문제 세부 정보

ProblemDetailsOptionsCustomizeProblemDetails와 함께 사용하는 다른 방법은 미들웨어에서 ProblemDetails를 설정하는 것입니다. IProblemDetailsService.WriteAsync를 호출하여 문제 세부 정보 응답을 작성할 수 있습니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

앞의 코드에서 최소 API 엔드포인트 /divide/squareroot는 오류 입력에 대해 예상되는 사용자 지정 문제 응답을 반환합니다.

API 컨트롤러 엔드포인트는 사용자 지정 문제 응답이 아니라 오류 입력에 대한 기본 문제 응답을 반환합니다. API 컨트롤러가 응답 스트림에 기록되었기 때문에 기본 문제 응답이 반환됩니다. 오류 상태 코드에 대한 문제 세부 정보는 IProblemDetailsService.WriteAsync가 호출되고 응답이 다시 작성되지 않습니다.

다음 ValuesControllerBadRequestResult를 반환하며, 이는 응답 스트림에 쓰고 사용자 지정 문제 응답이 반환되지 않도록 합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

다음은 Values3ControllerControllerBase.Problem를 반환하여 예상되는 사용자 지정 문제 결과가 반환되도록 합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

예외에 대한 ProblemDetails 페이로드 생성

다음 앱을 고려합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

비개발 환경에서 예외가 발생하면 클라이언트에 반환되는 표준 ProblemDetails 응답은 다음과 같습니다.

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

대부분의 앱에서 위의 코드는 예외에 필요한 모든 코드입니다. 그러나 다음 섹션에서는 더 자세한 문제 응답을 가져오는 방법을 보여줍니다.

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 오류에 액세스하고 IProblemDetailsService.WriteAsync를 사용하여 문제 세부 정보 응답을 작성할 수 있습니다.

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

문제 세부 정보를 생성하는 다른 방법은 예외 및 클라이언트 오류를 문제 세부 정보에 매핑하는 데 사용할 수 있는 타사 NuGet 패키지 Hellang.Middleware.ProblemDetails를 사용하는 것입니다.

추가 리소스

작성자: Tom Dykstra

이 항목에서는 ASP.NET Core 웹앱에서 오류를 처리하는 일반적인 접근법을 다룹니다. 웹 API에 대한 ASP.NET Core 컨트롤러 기반 웹 API의 핸들 오류를 참조하세요.

개발자 예외 페이지

개발자 예외 페이지에는 처리되지 않은 요청 예외에 대한 자세한 정보가 표시됩니다. ASP.NET Core 앱은 다음과 같은 경우 기본적으로 개발자 예외 페이지를 사용할 수 있습니다.

개발자 예외 페이지는 미들웨어 파이프라인의 앞부분에 실행되므로, 다음에 오는 미들웨어에서 throw된 미처리 예외를 catch할 수 있습니다.

앱이 프로덕션 환경에서 실행되는 경우에는 자세한 예외 정보를 공개적으로 표시해서는 안 됩니다. 환경을 구성하는 방법에 대한 자세한 내용은 ASP.NET Core에서 여러 환경 사용을 참조하세요.

개발자 예외 페이지에는 예외 및 요청에 대한 다음 정보가 포함될 수 있습니다.

  • 스택 추적
  • 쿼리 문자열 매개 변수(있는 경우)
  • 쿠키(있는 경우)
  • 헤더

개발자 예외 페이지는 어떠한 정보 제공도 보장하지 않습니다. 전체 오류 정보를 보려면 로깅을 사용하세요.

예외 처리기 페이지

프로덕션 환경의 사용자 지정 오류 처리 페이지를 구성하려면 UseExceptionHandler를 호출합니다. 이 예외 처리 미들웨어에서 다음을 수행합니다.

  • 미처리 예외를 catch하고 기록합니다.
  • 대체 파이프라인에서 표시된 경로를 사용하여 요청을 다시 실행합니다. 응답이 시작된 경우에는 요청이 다시 실행되지 않습니다. 템플릿 생성 코드는 /Error 경로를 사용하여 요청을 다시 실행합니다.

Warning

대체 파이프라인이 자체 예외를 throw하는 경우 예외 처리 미들웨어가 원래 예외를 다시 throw합니다.

다음 예제에서 UseExceptionHandler는 개발 환경이 아닌 환경에서 예외 처리 미들웨어를 추가합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Razor Pages 앱 템플릿은 Pages 폴더에 오류 페이지(.cshtmlPageModel클래스ErrorModel)를 제공합니다. MVC 앱의 프로젝트 템플릿에는 Home 컨트롤러에 대한 Error 작업 메서드와 오류 보기가 포함됩니다.

예외 처리 미들웨어는 원래 HTTP 메서드를 사용하여 요청을 다시 실행합니다. 오류 처리기 엔드포인트가 특정 HTTP 메서드 집합으로 제한되는 경우 해당 HTTP 메서드에 대해서만 실행됩니다. 예를 들어 [HttpGet] 특성을 사용하는 MVC 컨트롤러 작업은 GET 요청에 대해서만 실행됩니다. 모든 요청이 사용자 지정 오류 처리 페이지에 도달하도록 하려면 특정 HTTP 메서드 집합으로 제한하지 마세요.

원래 HTTP 메서드에 따라 예외를 서로 다르게 처리하려면 다음을 수행합니다.

  • Razor Pages의 경우 여러 처리기 메서드를 만듭니다. 예를 들어 OnGet을 사용하여 GET 예외를 처리하고 OnPost를 사용하여 POST 예외를 처리합니다.
  • MVC의 경우 여러 작업에 HTTP 동사 특성을 적용합니다. 예를 들어 [HttpGet]을 사용하여 GET 예외를 처리하고 [HttpPost]를 사용하여 POST 예외를 처리합니다.

인증되지 않은 사용자가 사용자 지정 오류 처리 페이지를 볼 수 있도록 하려면 익명 액세스를 지원해야 합니다.

예외에 액세스

IExceptionHandlerPathFeature를 사용하여 오류 처리기에서 예외 및 원래 요청 경로에 액세스합니다. 다음 예제에서는 IExceptionHandlerPathFeature를 사용하여 throw된 예외에 대한 자세한 정보를 가져옵니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

예외 처리기 람다

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 응답을 반환하기 전에 오류에 액세스할 수 있습니다.

다음 코드에서는 예외 처리에 람다를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

UseStatusCodePages

기본적으로 ASP.NET Core 앱은 ‘404 - 찾을 수 없음’과 같은 HTTP 오류 상태 코드에 대한 상태 코드 페이지를 제공하지 않습니다. 앱은 본문이 없는 HTTP 400-599 오류 상태 코드를 설정하면 상태 코드와 빈 응답 본문을 반환합니다. 일반적인 오류 상태 코드에 대해 기본 텍스트 전용 처리기를 사용하려면 Program.cs에서 UseStatusCodePages를 호출합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

요청 처리 미들웨어보다 먼저 UseStatusCodePages를 호출합니다. 예를 들어 정적 파일 미들웨어 및 엔드포인트 미들웨어보다 먼저 UseStatusCodePages를 호출합니다.

UseStatusCodePages를 사용하지 않는 경우 엔드포인트가 없는 URL로 이동하면 엔드포인트를 찾을 수 없다는 브라우저 종속 오류 메시지가 반환됩니다. UseStatusCodePages가 호출되면 브라우저에서 다음 응답을 반환합니다.

Status Code: 404; Not Found

UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

참고 항목

상태 코드 페이지 미들웨어는 예외를 catch하지 않습니다. 사용자 지정 오류 처리 페이지를 제공하려면 예외 처리기 페이지를 사용하세요.

형식 문자열을 사용하는 UseStatusCodePages

응답 콘텐츠 형식 및 텍스트를 사용자 지정하려면 콘텐츠 형식 및 형식 문자열을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

위의 코드에서 {0}은 오류 코드에 대한 자리 표시자입니다.

형식 문자열이 있는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

람다를 사용하는 UseStatusCodePages

사용자 지정 오류 처리 및 응답 쓰기 코드를 지정하려면 람다 식을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

람다를 사용하는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects 확장 메서드는:

  • ‘302 - 찾음’ 상태 코드를 클라이언트에 보냅니다.
  • URL 템플릿에 제공된 오류 처리 엔드포인트로 클라이언트를 리디렉션합니다. 오류 처리 엔드포인트는 일반적으로 오류 정보를 표시하고 HTTP 200을 반환합니다.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

위의 코드에 표시된 것처럼 URL 템플릿에는 상태 코드에 대한 {0} 자리 표시자가 포함될 수 있습니다. URL 템플릿이 ~(물결표)로 시작하는 경우 ~는 앱의 PathBase로 대체됩니다. 앱에서 엔드포인트를 지정할 때 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 일반적으로 다른 앱이 오류를 처리하는 상황에서 앱이 클라이언트를 다른 엔드포인트로 리디렉션해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 리디렉션된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 초기 리디렉션 응답과 함께 반환하면 안 되는 경우

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute 확장 메서드는:

  • 원래 상태 코드를 클라이언트에 반환합니다.
  • 대체 경로에서 요청 파이프라인을 다시 실행하여 응답 본문을 생성합니다.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

앱 내에 엔드포인트가 지정된 경우 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 다른 엔드포인트로 리디렉션하지 않고 요청을 처리해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 원래 요청된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 응답과 함께 반환해야 하는 경우

URL 템플릿은 /로 시작해야 하며 상태 코드에 대한 자리 표시자 {0}을 포함할 수 있습니다. 상태 코드를 쿼리 문자열 매개 변수로 전달하려면 두 번째 인수를 UseStatusCodePagesWithReExecute에 전달합니다. 예시:

app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

오류를 처리하는 엔드포인트는 다음 예제와 같이 오류를 생성한 원래 URL을 가져올 수 있습니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = string.Join(
                statusCodeReExecuteFeature.OriginalPathBase,
                statusCodeReExecuteFeature.OriginalPath,
                statusCodeReExecuteFeature.OriginalQueryString);
        }
    }
}

상태 코드 페이지 사용 안 함

MVC 컨트롤러 또는 작업 메서드에 대한 상태 코드 페이지를 비활성화하려면 [SkipStatusCodePages] 특성을 사용합니다.

Razor Pages 처리기 메서드 또는 MVC 컨트롤러에서 특정 요청에 대한 상태 코드 페이지를 사용하지 않으려면 IStatusCodePagesFeature를 사용합니다.

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

예외 처리 코드

예외 처리 페이지의 코드도 예외를 throw할 수 있습니다. 프로덕션 오류 페이지는 철저히 테스트하고 자체적으로 예외를 throw하지 않도록 특히 주의해야 합니다.

응답 헤더

또한 응답의 헤더가 전송되고 나면 다음 제한이 적용됩니다.

  • 앱에서 응답의 상태 코드를 변경할 수 없습니다.
  • 예외 페이지 또는 처리기를 실행할 수 없습니다. 응답을 완료하거나 연결이 중단되어야 합니다.

서버 예외 처리

앱의 예외 처리 논리 외에도 HTTP 서버 구현에서 몇 가지 예외를 처리할 수 있습니다. 응답 헤더가 전송되기 전에 예외를 catch한 서버는 응답 본문 없이 500 - Internal Server Error 응답을 보냅니다. 응답 헤더가 전송된 후에 예외를 catch한 서버는 연결을 닫습니다. 앱에서 처리되지 않는 요청은 서버에서 처리됩니다. 서버에서 요청을 처리할 때 발생하는 모든 예외는 서버의 예외 처리에 의해 처리됩니다. 앱의 사용자 지정 오류 페이지, 예외 처리 미들웨어 및 필터는 이 동작에 영향을 미치지 않습니다.

시작 예외 처리

호스팅 계층만 앱 시작 시 발생하는 예외를 처리할 수 있습니다. 시작 오류를 캡처하고 자세한 오류를 캡처하도록 호스트를 구성할 수 있습니다.

호스팅 계층은 호스트 주소/포트 바인딩 후에 오류가 발생하는 경우에만 캡처된 시작 오류에 대한 오류 페이지만 표시할 수 있습니다. 바인딩이 실패하면 결과는 다음과 같습니다.

  • 호스팅 계층에서 심각한 예외를 기록합니다.
  • dotnet 프로세스의 작동이 중단됩니다.
  • HTTP 서버가 Kestrel인 경우 오류 페이지가 표시되지 않습니다.

IIS(또는 Azure App Service) 또는 IIS Express에서 실행 중일 때, 프로세스를 시작할 수 없는 경우 ASP.NET Core 모듈이 ‘502.5 - 프로세스 실패’를 반환합니다. 자세한 내용은 Azure App Service 및 IIS에서 ASP.NET Core 문제 해결을 참조하세요.

데이터베이스 오류 페이지

데이터베이스 개발자 페이지 예외 필터 AddDatabaseDeveloperPageExceptionFilter는 Entity Framework Core 마이그레이션을 사용하여 해결할 수 있는 데이터베이스 관련 예외를 캡처합니다. 이 예외가 발생하면 문제 해결을 위해 가능한 작업의 세부 정보가 포함된 HTML 응답이 생성됩니다. 이 페이지는 개발 환경에서만 사용할 수 있습니다. 다음 코드는 데이터베이스 개발자 페이지 예외 필터를 추가합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

예외 필터

MVC 앱에서는 예외 필터를 전역으로 구성하거나 컨트롤러별 또는 작업별로 구성할 수 있습니다. Razor Pages 앱에서는 전역으로 구성하거나 페이지 모델별로 구성할 수 있습니다. 이러한 필터는 컨트롤러 작업 또는 다른 필터를 실행하는 동안 발생하는 처리되지 않은 예외를 처리합니다. 자세한 내용은 ASP.NET Core의 필터를 참조하세요.

예외 필터는 MVC 작업 내에서 발생하는 예외를 트래핑하는 데 유용하지만 기본 제공 예외 처리 미들웨어 UseExceptionHandler만큼 유연하지는 않습니다. 선택한 MVC 작업에 따라 오류 처리를 다르게 수행해야 하는 경우에만 UseExceptionHandler를 사용하는 것이 좋습니다.

모델 상태 오류

모델 상태 오류를 처리하는 방법에 대한 자세한 내용은 모델 바인딩모델 유효성 검사를 참조하세요.

추가 리소스

작성자: Kirk Larkin, Tom DykstraSteve Smith

이 항목에서는 ASP.NET Core 웹앱에서 오류를 처리하는 일반적인 접근법을 다룹니다. 웹 API에 대한 ASP.NET Core 컨트롤러 기반 웹 API의 핸들 오류를 참조하세요.

샘플 코드 보기 또는 다운로드 (다운로드하는 방법) F12 브라우저 개발자 도구의 네트워크 탭은 샘플 앱을 테스트할 때 유용합니다.

개발자 예외 페이지

개발자 예외 페이지에는 처리되지 않은 요청 예외에 대한 자세한 정보가 표시됩니다. ASP.NET Core 템플릿에서 다음 코드를 생성합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

위의 강조 표시된 코드는 앱이 개발 환경에서 실행 중인 경우 개발자 예외 페이지를 사용하도록 설정합니다.

템플릿은 미들웨어 파이프라인의 앞부분에 UseDeveloperExceptionPage를 배치하여, 다음에 오는 미들웨어에서 throw된 미처리 예외를 catch할 수 있도록 합니다.

위의 코드는 앱이 개발 환경에서 실행되는 경우에 개발자 예외 페이지를 사용하도록 설정합니다. 앱이 프로덕션 환경에서 실행되는 경우에는 자세한 예외 정보를 공개적으로 표시해서는 안 됩니다. 환경을 구성하는 방법에 대한 자세한 내용은 ASP.NET Core에서 여러 환경 사용을 참조하세요.

개발자 예외 페이지에는 예외 및 요청에 대한 다음 정보가 포함될 수 있습니다.

  • 스택 추적
  • 쿼리 문자열 매개 변수(있는 경우)
  • 쿠키(있는 경우)
  • 헤더

개발자 예외 페이지는 어떠한 정보 제공도 보장하지 않습니다. 전체 오류 정보를 보려면 로깅을 사용하세요.

예외 처리기 페이지

프로덕션 환경의 사용자 지정 오류 처리 페이지를 구성하려면 UseExceptionHandler를 호출합니다. 이 예외 처리 미들웨어에서 다음을 수행합니다.

  • 미처리 예외를 catch하고 기록합니다.
  • 대체 파이프라인에서 표시된 경로를 사용하여 요청을 다시 실행합니다. 응답이 시작된 경우에는 요청이 다시 실행되지 않습니다. 템플릿 생성 코드는 /Error 경로를 사용하여 요청을 다시 실행합니다.

Warning

대체 파이프라인이 자체 예외를 throw하는 경우 예외 처리 미들웨어가 원래 예외를 다시 throw합니다.

다음 예제에서 UseExceptionHandler는 개발 환경이 아닌 환경에서 예외 처리 미들웨어를 추가합니다.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Razor Pages 앱 템플릿은 Pages 폴더에 오류 페이지(.cshtmlPageModel클래스ErrorModel)를 제공합니다. MVC 앱의 프로젝트 템플릿에는 Home 컨트롤러에 대한 Error 작업 메서드와 오류 보기가 포함됩니다.

예외 처리 미들웨어는 원래 HTTP 메서드를 사용하여 요청을 다시 실행합니다. 오류 처리기 엔드포인트가 특정 HTTP 메서드 집합으로 제한되는 경우 해당 HTTP 메서드에 대해서만 실행됩니다. 예를 들어 [HttpGet] 특성을 사용하는 MVC 컨트롤러 작업은 GET 요청에 대해서만 실행됩니다. 모든 요청이 사용자 지정 오류 처리 페이지에 도달하도록 하려면 특정 HTTP 메서드 집합으로 제한하지 마세요.

원래 HTTP 메서드에 따라 예외를 서로 다르게 처리하려면 다음을 수행합니다.

  • Razor Pages의 경우 여러 처리기 메서드를 만듭니다. 예를 들어 OnGet을 사용하여 GET 예외를 처리하고 OnPost를 사용하여 POST 예외를 처리합니다.
  • MVC의 경우 여러 작업에 HTTP 동사 특성을 적용합니다. 예를 들어 [HttpGet]을 사용하여 GET 예외를 처리하고 [HttpPost]를 사용하여 POST 예외를 처리합니다.

인증되지 않은 사용자가 사용자 지정 오류 처리 페이지를 볼 수 있도록 하려면 익명 액세스를 지원해야 합니다.

예외에 액세스

IExceptionHandlerPathFeature를 사용하여 오류 처리기에서 예외 및 원래 요청 경로에 액세스합니다. 다음 코드는 ASP.NET Core 템플릿에서 생성된 기본값 Pages/Error.cshtml.cs 에 추가 ExceptionMessage 됩니다.

[ResponseCache(Duration=0, Location=ResponseCacheLocation.None, NoStore=true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    public string ExceptionMessage { get; set; }
    private readonly ILogger<ErrorModel> _logger;

    public ErrorModel(ILogger<ErrorModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
        HttpContext.Features.Get<IExceptionHandlerPathFeature>();
        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "File error thrown";
            _logger.LogError(ExceptionMessage);
        }
        if (exceptionHandlerPathFeature?.Path == "/index")
        {
            ExceptionMessage += " from home page";
        }
    }
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

샘플 앱에서 예외를 테스트하려면 다음을 수행합니다.

  • 환경을 프로덕션으로 설정합니다.
  • Program.cswebBuilder.UseStartup<Startup>();에서 주석을 제거합니다.
  • 페이지에서 예외 트리거를 home 선택합니다.

예외 처리기 람다

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 응답을 반환하기 전에 오류에 액세스할 수 있습니다.

다음 코드에서는 예외 처리에 람다를 사용합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(errorApp =>
        {
            errorApp.Run(async context =>
            {
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;;
                context.Response.ContentType = "text/html";

                await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
                await context.Response.WriteAsync("ERROR!<br><br>\r\n");

                var exceptionHandlerPathFeature =
                    context.Features.Get<IExceptionHandlerPathFeature>();

                if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
                {
                    await context.Response.WriteAsync(
                                              "File error thrown!<br><br>\r\n");
                }

                await context.Response.WriteAsync(
                                              "<a href=\"/\">Home</a><br>\r\n");
                await context.Response.WriteAsync("</body></html>\r\n");
                await context.Response.WriteAsync(new string(' ', 512)); 
            });
        });
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Warning

IExceptionHandlerFeature 또는 IExceptionHandlerPathFeature에서 클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

샘플 앱에서 예외 처리 람다를 테스트하려면 다음을 수행합니다.

  • 환경을 프로덕션으로 설정합니다.
  • Program.cswebBuilder.UseStartup<StartupLambda>();에서 주석을 제거합니다.
  • 페이지에서 예외 트리거를 home 선택합니다.

UseStatusCodePages

기본적으로 ASP.NET Core 앱은 ‘404 - 찾을 수 없음’과 같은 HTTP 오류 상태 코드에 대한 상태 코드 페이지를 제공하지 않습니다. 앱은 본문이 없는 HTTP 400-599 오류 상태 코드를 설정하면 상태 코드와 빈 응답 본문을 반환합니다. 상태 코드 페이지를 제공하려면 상태 코드 페이지 미들웨어를 사용합니다. 일반적인 오류 상태 코드에 대해 기본 텍스트 전용 처리기를 사용하려면 Startup.Configure 메서드에서 UseStatusCodePages를 호출합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages();

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

요청 처리 미들웨어보다 먼저 UseStatusCodePages를 호출합니다. 예를 들어 정적 파일 미들웨어 및 엔드포인트 미들웨어보다 먼저 UseStatusCodePages를 호출합니다.

UseStatusCodePages를 사용하지 않는 경우 엔드포인트가 없는 URL로 이동하면 엔드포인트를 찾을 수 없다는 브라우저 종속 오류 메시지가 반환됩니다. 예를 들어 Home/Privacy2로 이동하는 경우 UseStatusCodePages를 호출하면 브라우저에서 다음을 반환합니다.

Status Code: 404; Not Found

UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

샘플 앱에서 UseStatusCodePages를 테스트하려면 다음을 수행합니다.

  • 환경을 프로덕션으로 설정합니다.
  • Program.cswebBuilder.UseStartup<StartupUseStatusCodePages>();에서 주석을 제거합니다.
  • 페이지의 home 링크를 home 선택합니다.

참고 항목

상태 코드 페이지 미들웨어는 예외를 catch하지 않습니다. 사용자 지정 오류 처리 페이지를 제공하려면 예외 처리기 페이지를 사용하세요.

형식 문자열을 사용하는 UseStatusCodePages

응답 콘텐츠 형식 및 텍스트를 사용자 지정하려면 콘텐츠 형식 및 형식 문자열을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages(
        "text/plain", "Status code page, status code: {0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

위의 코드에서 {0}은 오류 코드에 대한 자리 표시자입니다.

형식 문자열이 있는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

샘플 앱에서 UseStatusCodePages를 테스트하려면 Program.cswebBuilder.UseStartup<StartupFormat>();에서 주석을 제거합니다.

람다를 사용하는 UseStatusCodePages

사용자 지정 오류 처리 및 응답 쓰기 코드를 지정하려면 람다 식을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages(async context =>
    {
        context.HttpContext.Response.ContentType = "text/plain";

        await context.HttpContext.Response.WriteAsync(
            "Status code page, status code: " +
            context.HttpContext.Response.StatusCode);
    });

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

람다를 사용하는 UseStatusCodePages는 사용자에게 유용하지 않은 메시지를 반환하기 때문에 일반적으로 프로덕션에서 사용되지 않습니다.

샘플 앱에서 UseStatusCodePages를 테스트하려면 Program.cswebBuilder.UseStartup<StartupStatusLambda>();에서 주석을 제거합니다.

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects 확장 메서드는:

  • ‘302 - 찾음’ 상태 코드를 클라이언트에 보냅니다.
  • URL 템플릿에 제공된 오류 처리 엔드포인트로 클라이언트를 리디렉션합니다. 오류 처리 엔드포인트는 일반적으로 오류 정보를 표시하고 HTTP 200을 반환합니다.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithRedirects("/MyStatusCode?code={0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

위의 코드에 표시된 것처럼 URL 템플릿에는 상태 코드에 대한 {0} 자리 표시자가 포함될 수 있습니다. URL 템플릿이 ~(물결표)로 시작하는 경우 ~는 앱의 PathBase로 대체됩니다. 앱에서 엔드포인트를 지정할 때 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다. Razor Pages 예제는 샘플 앱Pages/MyStatusCode.cshtml을 참조하세요.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 일반적으로 다른 앱이 오류를 처리하는 상황에서 앱이 클라이언트를 다른 엔드포인트로 리디렉션해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 리디렉션된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 초기 리디렉션 응답과 함께 반환하면 안 되는 경우

샘플 앱에서 UseStatusCodePages를 테스트하려면 Program.cswebBuilder.UseStartup<StartupSCredirect>();에서 주석을 제거합니다.

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute 확장 메서드는:

  • 원래 상태 코드를 클라이언트에 반환합니다.
  • 대체 경로에서 요청 파이프라인을 다시 실행하여 응답 본문을 생성합니다.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithReExecute("/MyStatusCode2", "?code={0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

앱 내에 엔드포인트가 지정된 경우 해당 엔드포인트에 대한 MVC 보기 또는 Razor 페이지를 만듭니다. 요청이 상태 페이지로 다시 라우팅될 수 있도록 UseRouting 전에 UseStatusCodePagesWithReExecute를 배치합니다. Razor Pages 예제는 샘플 앱Pages/MyStatusCode2.cshtml을 참조하세요.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 다른 엔드포인트로 리디렉션하지 않고 요청을 처리해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 원래 요청된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 응답과 함께 반환해야 하는 경우

URL 및 쿼리 문자열 템플릿에는 상태 코드에 대한 자리 표시자({0})가 포함될 수 있습니다. URL 템플릿은 /로 시작해야 합니다.

오류를 처리하는 엔드포인트는 다음 예제와 같이 오류를 생성한 원래 URL을 가져올 수 있습니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class MyStatusCode2Model : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string ErrorStatusCode { get; set; }

    public string OriginalURL { get; set; }
    public bool ShowOriginalURL => !string.IsNullOrEmpty(OriginalURL);

    public void OnGet(string code)
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
        ErrorStatusCode = code;

        var statusCodeReExecuteFeature = HttpContext.Features.Get<
                                               IStatusCodeReExecuteFeature>();
        if (statusCodeReExecuteFeature != null)
        {
            OriginalURL =
                statusCodeReExecuteFeature.OriginalPathBase
                + statusCodeReExecuteFeature.OriginalPath
                + statusCodeReExecuteFeature.OriginalQueryString;
        }
    }
}

Razor Pages 예제는 샘플 앱Pages/MyStatusCode2.cshtml을 참조하세요.

샘플 앱에서 UseStatusCodePages를 테스트하려면 Program.cswebBuilder.UseStartup<StartupSCreX>();에서 주석을 제거합니다.

상태 코드 페이지 사용 안 함

MVC 컨트롤러 또는 작업 메서드에 대한 상태 코드 페이지를 비활성화하려면 [SkipStatusCodePages] 특성을 사용합니다.

Razor Pages 처리기 메서드 또는 MVC 컨트롤러에서 특정 요청에 대한 상태 코드 페이지를 사용하지 않으려면 IStatusCodePagesFeature를 사용합니다.

public void OnGet()
{
    // using Microsoft.AspNetCore.Diagnostics;
    var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature != null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

예외 처리 코드

예외 처리 페이지의 코드도 예외를 throw할 수 있습니다. 프로덕션 오류 페이지는 철저히 테스트하고 자체적으로 예외를 throw하지 않도록 특히 주의해야 합니다.

응답 헤더

또한 응답의 헤더가 전송되고 나면 다음 제한이 적용됩니다.

  • 앱에서 응답의 상태 코드를 변경할 수 없습니다.
  • 예외 페이지 또는 처리기를 실행할 수 없습니다. 응답을 완료하거나 연결이 중단되어야 합니다.

서버 예외 처리

앱의 예외 처리 논리 외에도 HTTP 서버 구현에서 몇 가지 예외를 처리할 수 있습니다. 응답 헤더가 전송되기 전에 예외를 catch한 서버는 응답 본문 없이 500 - Internal Server Error 응답을 보냅니다. 응답 헤더가 전송된 후에 예외를 catch한 서버는 연결을 닫습니다. 앱에서 처리되지 않는 요청은 서버에서 처리됩니다. 서버에서 요청을 처리할 때 발생하는 모든 예외는 서버의 예외 처리에 의해 처리됩니다. 앱의 사용자 지정 오류 페이지, 예외 처리 미들웨어 및 필터는 이 동작에 영향을 미치지 않습니다.

시작 예외 처리

호스팅 계층만 앱 시작 시 발생하는 예외를 처리할 수 있습니다. 시작 오류를 캡처하고 자세한 오류를 캡처하도록 호스트를 구성할 수 있습니다.

호스팅 계층은 호스트 주소/포트 바인딩 후에 오류가 발생하는 경우에만 캡처된 시작 오류에 대한 오류 페이지만 표시할 수 있습니다. 바인딩이 실패하면 결과는 다음과 같습니다.

  • 호스팅 계층에서 심각한 예외를 기록합니다.
  • dotnet 프로세스의 작동이 중단됩니다.
  • HTTP 서버가 Kestrel인 경우 오류 페이지가 표시되지 않습니다.

IIS(또는 Azure App Service) 또는 IIS Express에서 실행 중일 때, 프로세스를 시작할 수 없는 경우 ASP.NET Core 모듈이 ‘502.5 - 프로세스 실패’를 반환합니다. 자세한 내용은 Azure App Service 및 IIS에서 ASP.NET Core 문제 해결을 참조하세요.

데이터베이스 오류 페이지

데이터베이스 개발자 페이지 예외 필터 AddDatabaseDeveloperPageExceptionFilter는 Entity Framework Core 마이그레이션을 사용하여 해결할 수 있는 데이터베이스 관련 예외를 캡처합니다. 이 예외가 발생하면 문제 해결을 위해 가능한 작업의 세부 정보가 포함된 HTML 응답이 생성됩니다. 이 페이지는 개발 환경에서만 사용할 수 있습니다. 다음 코드는 개별 사용자 계정을 지정할 때 ASP.NET Core Razor 페이지 템플릿에서 생성되었습니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

예외 필터

MVC 앱에서는 예외 필터를 전역으로 구성하거나 컨트롤러별 또는 작업별로 구성할 수 있습니다. Razor Pages 앱에서는 전역으로 구성하거나 페이지 모델별로 구성할 수 있습니다. 이러한 필터는 컨트롤러 작업 또는 다른 필터를 실행하는 동안 발생하는 처리되지 않은 예외를 처리합니다. 자세한 내용은 ASP.NET Core의 필터를 참조하세요.

예외 필터는 MVC 작업 내에서 발생하는 예외를 트래핑하는 데 유용하지만 기본 제공 예외 처리 미들웨어 UseExceptionHandler만큼 유연하지는 않습니다. 선택한 MVC 작업에 따라 오류 처리를 다르게 수행해야 하는 경우에만 UseExceptionHandler를 사용하는 것이 좋습니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

모델 상태 오류

모델 상태 오류를 처리하는 방법에 대한 자세한 내용은 모델 바인딩모델 유효성 검사를 참조하세요.

추가 리소스

작성자: Tom DykstraSteve Smith

이 항목에서는 ASP.NET Core 웹앱에서 오류를 처리하는 일반적인 접근법을 다룹니다. 웹 API에 대한 ASP.NET Core 컨트롤러 기반 웹 API의 핸들 오류를 참조하세요.

샘플 코드 보기 또는 다운로드 (다운로드하는 방법)

개발자 예외 페이지

개발자 예외 페이지에요청 예외에 대한 자세한 정보가 표시됩니다. ASP.NET Core 템플릿에서 다음 코드를 생성합니다.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

위의 코드는 앱이 개발 환경에서 실행 중인 경우 개발자 예외 페이지를 사용하도록 설정합니다.

템플릿은 미들웨어 앞에 UseDeveloperExceptionPage를 배치하여 다음에 오는 미들웨어에서 예외를 catch할 수 있도록 합니다.

위의 코드에서는 앱이 개발 환경에서 실행 중인 경우에만 개발자 예외 페이지를 사용하도록 설정합니다. 앱이 프로덕션에서 실행되는 경우에는 자세한 예외 정보를 공개적으로 표시해서는 안 됩니다. 환경을 구성하는 방법에 대한 자세한 내용은 ASP.NET Core에서 여러 환경 사용을 참조하세요.

개발자 예외 페이지에는 예외 및 요청에 대한 다음 정보가 포함됩니다.

  • 스택 추적
  • 쿼리 문자열 매개 변수(있는 경우)
  • 쿠키(있는 경우)
  • 헤더

예외 처리기 페이지

프로덕션 환경의 사용자 지정 오류 처리 페이지를 구성하려면 예외 처리 미들웨어를 사용합니다. 미들웨어는 다음을 수행합니다.

  • 예외를 잡고 기록합니다.
  • 대체 파이프라인에서 표시된 페이지 또는 컨트롤러에 대한 요청을 다시 실행합니다. 응답이 시작된 경우에는 요청이 다시 실행되지 않습니다. 템플릿 생성 코드는 /Error에 대한 요청을 다시 실행합니다.

다음 예제에서 UseExceptionHandler는 비 개발 환경에서 예외 처리 미들웨어를 추가합니다.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Razor Pages 앱 템플릿은 Pages 폴더에 오류 페이지(.cshtmlPageModel클래스ErrorModel)를 제공합니다. MVC 앱의 프로젝트 템플릿에는 Home 컨트롤러에 대한 오류 작업 메서드와 오류 보기가 포함됩니다.

오류 처리기 작업 메서드를 HttpGet와 같은 HTTP 메서드 특성을 사용하여 표시하지 마세요. 명시적 동사는 일부 요청이 메서드에 도달하지 않도록 방해합니다. 인증되지 않은 사용자에게 오류 보기를 표시해야 하는 경우 메서드에 대한 익명 액세스를 허용합니다.

예외에 액세스

IExceptionHandlerPathFeature를 사용하여 오류 처리기 컨트롤러 또는 페이지에서 예외 및 원래 요청 경로에 액세스합니다.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    public string ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();
        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "File error thrown";
        }
        if (exceptionHandlerPathFeature?.Path == "/index")
        {
            ExceptionMessage += " from home page";
        }
    }
}

Warning

클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

위의 예외 처리 페이지를 트리거하려면 환경을 프로덕션으로 설정하고 예외를 적용합니다.

예외 처리기 람다

사용자 지정 예외 처리기 페이지의 대안은 UseExceptionHandler에 람다를 제공하는 것입니다. 람다를 사용하면 응답을 반환하기 전에 오류에 액세스할 수 있습니다.

다음은 예외 처리를 위해 람다를 사용하는 예제입니다.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
   app.UseExceptionHandler(errorApp =>
   {
        errorApp.Run(async context =>
        {
            context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
            context.Response.ContentType = "text/html";

            await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
            await context.Response.WriteAsync("ERROR!<br><br>\r\n");

            var exceptionHandlerPathFeature = 
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync("File error thrown!<br><br>\r\n");
            }

            await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n");
            await context.Response.WriteAsync("</body></html>\r\n");
            await context.Response.WriteAsync(new string(' ', 512)); // IE padding
        });
    });
    app.UseHsts();
}

위의 코드에서 Internet Explorer 브라우저에 IE 오류 메시지가 아닌 오류 메시지가 표시되도록 await context.Response.WriteAsync(new string(' ', 512));가 추가됩니다. 자세한 내용은 해당 GitHub 이슈를 참조하세요.

Warning

IExceptionHandlerFeature 또는 IExceptionHandlerPathFeature에서 클라이언트에 중요한 오류 정보를 제공하지 마세요. 오류 제공은 보안 위험입니다.

샘플 앱에서 람다를 처리하는 예외의 결과를 보려면 전 ErrorHandlerLambda 처리기 지시문을 사용하고 ProdEnvironment 페이지에서 예외 트리거를 home 선택합니다.

UseStatusCodePages

기본적으로 ASP.NET Core 앱은 ‘404 - 찾을 수 없음’과 같은 HTTP 상태 코드에 대한 상태 코드 페이지를 제공하지 않습니다. 앱은 상태 코드와 빈 응답 본문을 반환합니다. 상태 코드 페이지를 제공하려면 상태 코드 페이지 미들웨어를 사용합니다.

미들웨어는 Microsoft.AspNetCore.Diagnostics 패키지를 통해 사용할 수 있습니다.

일반적인 오류 상태 코드에 대해 기본 텍스트 전용 처리기를 사용하려면 Startup.Configure 메서드에서 UseStatusCodePages를 호출합니다.

app.UseStatusCodePages();

요청 처리 미들웨어(예: 정적 파일 미들웨어 및 MVC 미들웨어) 이전에 UseStatusCodePages를 호출합니다.

UseStatusCodePages를 사용하지 않는 경우 엔드포인트가 없는 URL로 이동하면 엔드포인트를 찾을 수 없다는 브라우저 종속 오류 메시지가 반환됩니다. 예를 들어 Home/Privacy2로 이동하는 경우 UseStatusCodePages를 호출하면 브라우저에서 다음을 반환합니다.

Status Code: 404; Not Found

형식 문자열을 사용하는 UseStatusCodePages

응답 콘텐츠 형식 및 텍스트를 사용자 지정하려면 콘텐츠 형식 및 형식 문자열을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

app.UseStatusCodePages(
    "text/plain", "Status code page, status code: {0}");

람다를 사용하는 UseStatusCodePages

사용자 지정 오류 처리 및 응답 쓰기 코드를 지정하려면 람다 식을 사용하는 UseStatusCodePages의 오버로드를 사용합니다.

app.UseStatusCodePages(async context =>
{
    context.HttpContext.Response.ContentType = "text/plain";

    await context.HttpContext.Response.WriteAsync(
        "Status code page, status code: " + 
        context.HttpContext.Response.StatusCode);
});

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects 확장 메서드는:

  • ‘302 - 찾음’ 상태 코드를 클라이언트에 보냅니다.
  • 클라이언트를 URL 템플릿에 제공된 위치로 리디렉션합니다.
app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");

예제에서 볼 수 있는 것과 같이 URL 템플릿에는 상태 코드에 대한 {0} 자리 표시자가 포함될 수 있습니다. URL 템플릿이 ~(물결표)로 시작하는 경우 ~는 앱의 PathBase로 대체됩니다. 앱 내의 엔드포인트를 가리키는 경우 해당 엔드포인트에 대한 MVC 뷰 또는 Razor 페이지를 만들어야 합니다. Pages 예제는 Razor 샘플 앱을 참조 Pages/StatusCode.cshtml 하세요.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 일반적으로 다른 앱이 오류를 처리하는 상황에서 앱이 클라이언트를 다른 엔드포인트로 리디렉션해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 리디렉션된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 초기 리디렉션 응답과 함께 반환하면 안 되는 경우

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute 확장 메서드는:

  • 원래 상태 코드를 클라이언트에 반환합니다.
  • 대체 경로에서 요청 파이프라인을 다시 실행하여 응답 본문을 생성합니다.
app.UseStatusCodePagesWithReExecute("/StatusCode","?code={0}");

앱 내의 엔드포인트를 가리키는 경우 해당 엔드포인트에 대한 MVC 뷰 또는 Razor 페이지를 만들어야 합니다. 요청이 상태 페이지로 다시 라우팅될 수 있도록 UseRouting 전에 UseStatusCodePagesWithReExecute를 배치합니다. Pages 예제는 Razor 샘플 앱을 참조 Pages/StatusCode.cshtml 하세요.

이 메서드는 일반적으로 앱이 다음과 같은 경우에 사용됩니다.

  • 다른 엔드포인트로 리디렉션하지 않고 요청을 처리해야 하는 경우. 웹앱의 경우 클라이언트의 브라우저 주소 표시줄에 원래 요청된 엔드포인트가 반영됩니다.
  • 원래 상태 코드를 유지하고 응답과 함께 반환해야 하는 경우

URL 및 쿼리 문자열 템플릿에는 상태 코드에 대한 자리 표시자({0})가 포함될 수 있습니다. URL 템플릿은 슬래시(/)로 시작해야 합니다. 경로에서 자리 표시자를 사용하는 경우, 엔드포인트(페이지 또는 컨트롤러)가 경로 세그먼트를 처리할 수 있는지 확인하세요. 예를 들어 오류를 위한 Razor 페이지는 @page 지시문을 사용하여 선택적 경로 세그먼트 값을 수락해야 합니다.

@page "{code?}"

오류를 처리하는 엔드포인트는 다음 예제와 같이 오류를 생성한 원래 URL을 가져올 수 있습니다.

var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature != null)
{
    OriginalURL =
        statusCodeReExecuteFeature.OriginalPathBase
        + statusCodeReExecuteFeature.OriginalPath
        + statusCodeReExecuteFeature.OriginalQueryString;
}

오류 처리기 작업 메서드를 HttpGet와 같은 HTTP 메서드 특성을 사용하여 표시하지 마세요. 명시적 동사는 일부 요청이 메서드에 도달하지 않도록 방해합니다. 인증되지 않은 사용자에게 오류 보기를 표시해야 하는 경우 메서드에 대한 익명 액세스를 허용합니다.

상태 코드 페이지 사용 안 함

MVC 컨트롤러 또는 작업 메서드에 대한 상태 코드 페이지를 비활성화하려면 [SkipStatusCodePages] 특성을 사용합니다.

Razor Pages 처리기 메서드 또는 MVC 컨트롤러에서 특정 요청에 대한 상태 코드 페이지를 사용하지 않으려면 IStatusCodePagesFeature를 사용합니다.

var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();

if (statusCodePagesFeature != null)
{
    statusCodePagesFeature.Enabled = false;
}

예외 처리 코드

예외 처리 페이지의 코드도 예외를 던질 수 있습니다. 종종 프로덕션 오류 페이지에 대해 완전한 정적 콘텐츠를 구성하는 것이 좋습니다.

응답 헤더

또한 응답의 헤더가 전송되고 나면 다음 제한이 적용됩니다.

  • 앱에서 응답의 상태 코드를 변경할 수 없습니다.
  • 예외 페이지 또는 처리기를 실행할 수 없습니다. 응답을 완료하거나 연결이 중단되어야 합니다.

서버 예외 처리

앱의 예외 처리 논리 외에도 HTTP 서버 구현에서 몇 가지 예외를 처리할 수 있습니다. 응답 헤더가 전송되기 전에 예외를 catch한 서버는 응답 본문 없이 ‘500 - 내부 서버 오류’ 응답을 보냅니다. 응답 헤더가 전송된 후에 예외를 catch한 서버는 연결을 닫습니다. 앱으로 처리되지 않는 요청은 서버에서 처리됩니다. 서버에서 요청을 처리할 때 발생하는 모든 예외는 서버의 예외 처리에 의해 처리됩니다. 앱의 사용자 지정 오류 페이지, 예외 처리 미들웨어 및 필터는 이 동작에 영향을 미치지 않습니다.

시작 예외 처리

호스팅 계층만 앱 시작 시 발생하는 예외를 처리할 수 있습니다. 시작 오류를 캡처하고 자세한 오류를 캡처하도록 호스트를 구성할 수 있습니다.

호스팅 계층은 호스트 주소/포트 바인딩 후에 오류가 발생하는 경우에만 캡처된 시작 오류에 대한 오류 페이지만 표시할 수 있습니다. 바인딩이 실패하면 결과는 다음과 같습니다.

  • 호스팅 계층에서 심각한 예외를 기록합니다.
  • dotnet 프로세스의 작동이 중단됩니다.
  • HTTP 서버가 Kestrel인 경우 오류 페이지가 표시되지 않습니다.

IIS(또는 Azure App Service) 또는 IIS Express에서 실행 중일 때, 프로세스를 시작할 수 없는 경우 ASP.NET Core 모듈이 ‘502.5 - 프로세스 실패’를 반환합니다. 자세한 내용은 Azure App Service 및 IIS에서 ASP.NET Core 문제 해결을 참조하세요.

데이터베이스 오류 페이지

데이터베이스 오류 페이지 미들웨어는 Entity Framework 마이그레이션을 사용하여 해결할 수 있는 데이터베이스 관련 예외를 잡습니다. 이 예외가 발생하면 문제 해결을 위한 가능한 작업의 세부 정보가 포함된 HTML 응답이 생성됩니다. 이 페이지는 개발 환경에서만 사용하도록 설정해야 합니다. Startup.Configure에 코드를 추가하여 페이지를 사용하도록 설정합니다.

if (env.IsDevelopment())
{
    app.UseDatabaseErrorPage();
}

UseDatabaseErrorPage에는 Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet 패키지가 필요합니다.

예외 필터

MVC 앱에서는 예외 필터를 전역으로 구성하거나 컨트롤러별 또는 작업별로 구성할 수 있습니다. Razor Pages 앱에서는 전역으로 구성하거나 페이지 모델별로 구성할 수 있습니다. 이러한 필터는 컨트롤러 작업 또는 다른 필터를 실행하는 동안 발생하는 처리되지 않은 예외를 처리합니다. 자세한 내용은 ASP.NET Core의 필터를 참조하세요.

예외 필터는 MVC 작업 내에서 발생하는 예외를 트래핑하는 데 유용하지만 예외 처리 미들웨어만큼 유연하지는 않습니다. 미들웨어를 사용하는 것이 좋습니다. 선택한 MVC 작업에 따라 오류 처리를 다르게 수행해야 하는 경우에만 필터를 사용하세요.

모델 상태 오류

모델 상태 오류를 처리하는 방법에 대한 자세한 내용은 모델 바인딩모델 유효성 검사를 참조하세요.

추가 리소스