다음을 통해 공유


ASP.NET Core 컨트롤러 기반 웹 API에서 오류 처리

참고 항목

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

Warning

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

Important

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

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

이 문서에서는 컨트롤러 기반 ASP.NET Core 웹 API에서 오류를 처리하고 오류 처리를 사용자 지정하는 방법을 설명합니다. 최소 API 의 오류 처리에 대한 자세한 내용은 ASP.NET Core 의 오류 처리 및 최소 API의 오류 처리를 참조하세요.

개발자 예외 페이지

개발자 예외 페이지에는 처리되지 않은 요청 예외에 대한 자세한 정보가 표시됩니다. 이것은 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

개발자 예외 페이지를 보려면:

  • 컨트롤러 기반 API에 다음 컨트롤러 작업을 추가합니다. 엔드포인트가 요청되면 작업이 예외를 throw합니다.

    [HttpGet("Throw")]
    public IActionResult Throw() =>
        throw new Exception("Sample exception.");
    
  • 개발 환경에서 앱을 실행합니다.

  • 컨트롤러 작업으로 정의된 엔드포인트로 이동합니다.

예외 처리기

비개발 환경에서는 예외 처리 미들웨어를 사용하여 오류 페이로드를 생성할 수 있습니다.

  1. Program.cs에서 UseExceptionHandler를 호출하여 예외 처리 미들웨어를 추가합니다.

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. /error 경로에 응답하도록 컨트롤러 작업을 구성합니다.

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

위의 HandleError 작업은 RFC 7807 호환 페이로드를 클라이언트에 보냅니다.

Warning

오류 처리기 작업 메서드를 HttpGet와 같은 HTTP 메서드 특성을 사용하여 표시하지 마세요. 명시적 동사는 일부 요청이 작업 메서드에 도달하지 못하도록 합니다.

Swagger/OpenAPI를 사용하는 웹 API의 경우 오류 처리기 작업을 [ApiExplorerSettings] 특성으로 표시하고 해당 IgnoreApi 속성을 true로 설정합니다. 이 특성 구성은 앱의 OpenAPI 사양에서 오류 처리기 작업을 제외합니다.

[ApiExplorerSettings(IgnoreApi = true)]

인증되지 않은 사용자에게 오류를 표시해야 하는 경우 메서드에 대한 익명 액세스를 허용합니다.

예외 처리 미들웨어를 개발 환경에서 사용하여 모든 환경에서 일관된 페이로드 형식을 생성할 수도 있습니다.

  1. Program.cs에서 환경별 예외 처리 미들웨어 인스턴스를 등록합니다.

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    위의 코드에서 미들웨어는 다음에 등록됩니다.

    • 개발 환경에서 /error-development의 경로.
    • 비개발 환경에서 /error의 경로입니다.

  2. 개발 및 비개발 경로 모두에 대한 컨트롤러 작업을 추가합니다.

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

예외를 사용하여 응답 수정

응답의 내용은 사용자 지정 예외 및 작업 필터를 사용하여 컨트롤러 외부에서 수정할 수 있습니다.

  1. HttpResponseException이라는 잘 알려진 예외 형식을 만듭니다.

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. HttpResponseExceptionFilter이라는 작업 필터를 만듭니다.

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    위의 필터는 최대 정수 값에서 10을 뺀 Order를 지정합니다. 이 Order를 통해 파이프라인의 끝에서 다른 필터를 실행할 수 있습니다.

  3. Program.cs에서 필터 컬렉션에 작업 필터를 추가합니다.

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

유효성 검사 실패 오류 응답

Web API 컨트롤러의 경우, 모델 유효성 검사에 실패하면 MVC는 ValidationProblemDetails 응답 형식으로 응답합니다. MVC는 InvalidModelStateResponseFactory의 결과를 사용하여 유효성 검사 실패에 대한 오류 응답을 생성합니다. 다음 예제에서는 기본 팩터리를 Program.cs에서 XML로 형식 지정 응답도 지원하는 구현으로 바꿉니다.

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

클라이언트 오류 응답

오류 결과는 400 이상의 HTTP 상태 코드를 가진 결과로 정의됩니다. 웹 API 컨트롤러의 경우, MVC는 ProblemDetails를 생성하도록 오류 결과를 변환합니다.

오류 상태 코드에 대한 ProblemDetails의 자동 생성은 기본적으로 사용하도록 설정되지만 오류 응답은 다음 방법 중 하나로 구성할 수 있습니다.

  1. 문제 세부 정보 서비스 사용
  2. ProblemDetailsFactory 구현
  3. ApiBehaviorOptions.ClientErrorMapping 사용

기본 문제 세부 정보 응답

다음 Program.cs 파일은 API 컨트롤러용 웹 애플리케이션 템플릿에 의해 생성되었습니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

입력이 유효하지 않은 경우 BadRequest를 반환하는 다음 컨트롤러를 고려합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

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

다음 조건 중 하나라도 적용되는 경우 이전 코드와 함께 문제 세부 정보 응답이 생성됩니다.

  • /api/values2/divide 엔드포인트는 분모가 0으로 호출됩니다.
  • /api/values2/squareroot 엔드포인트는 0보다 작은 방사형으로 호출됩니다.

기본 문제 세부 정보 응답 본문에는 다음 type, titlestatus 값이 있습니다.

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}

문제 세부 정보 서비스

ASP.NET Core는 IProblemDetailsService를 사용하여 HTTP API에 대한 문제 세부 정보 만들기를 지원합니다. 자세한 내용은 문제 세부 정보 서비스를 참조하세요.

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

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

입력이 유효하지 않은 경우 반환 BadRequest 되는 이전 섹션의 API 컨트롤러를 고려합니다.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

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

다음 조건 중 하나라도 적용되는 경우 이전 코드와 함께 문제 세부 정보 응답이 생성됩니다.

  • 잘못된 입력이 제공됩니다.
  • URI에 일치하는 엔드포인트가 없습니다.
  • 처리되지 않은 예외가 발생합니다.

SuppressMapClientErrors 속성이 true로 설정된 경우 오류 상태 코드에 대한 ProblemDetails 자동 생성이 사용되지 않습니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressMapClientErrors = true;
    });

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

앞의 코드를 사용하여 API 컨트롤러가 BadRequest를 반환하면 응답 본문 없이 HTTP 400 응답 상태가 반환됩니다. SuppressMapClientErrors는 API 컨트롤러 엔드포인트에 대해 WriteAsync를 호출하는 경우에도 ProblemDetails 응답이 생성되지 않도록 방지합니다. WriteAsync는 이 문서 뒷부분에 설명되어 있습니다.

다음 섹션에서는 더 유용한 응답을 반환하도록 CustomizeProblemDetails를 사용하여 문제 세부 정보 응답 본문을 사용자 지정하는 방법을 보여 줍니다. 추가 사용자 지정 옵션은 문제 세부 정보 사용자 지정을 참조하세요.

CustomizeProblemDetails를 사용하여 문제 세부 정보 사용자 지정

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails(options =>
        options.CustomizeProblemDetails = (context) =>
        {

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

                context.ProblemDetails.Type = details.Type;
                context.ProblemDetails.Title = "Bad Input";
                context.ProblemDetails.Detail = details.Detail;
            }
        }
    );

var app = builder.Build();

app.UseHttpsRedirection();

app.UseStatusCodePages();

app.UseAuthorization();

app.MapControllers();

app.Run();

업데이트된 API 컨트롤러:

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

}

다음 코드에는 이전 샘플과 함께 사용되는 MathErrorFeatureMathErrorType이 포함되어 있습니다.

// Custom Http Request Feature
class MathErrorFeature
{
    public MathErrorType MathError { get; set; }
}

// Custom math errors
enum MathErrorType
{
    DivisionByZeroError,
    NegativeRadicandError
}

다음 조건 중 하나라도 적용되는 경우 이전 코드와 함께 문제 세부 정보 응답이 생성됩니다.

  • /divide 엔드포인트는 분모가 0으로 호출됩니다.
  • /squareroot 엔드포인트는 0보다 작은 방사형으로 호출됩니다.
  • URI에 일치하는 엔드포인트가 없습니다.

문제 세부 정보 응답 본문에는 두 squareroot 엔드포인트 중 하나가 0보다 작은 방사형으로 호출되는 경우 다음이 포함됩니다.

{
  "type": "https://en.wikipedia.org/wiki/Square_root",
  "title": "Bad Input",
  "status": 400,
  "detail": "Negative or complex numbers are not allowed."
}

샘플 코드 보기 또는 다운로드

도구 ProblemDetailsFactory

MVC는 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory를 사용하여 ProblemDetailsValidationProblemDetails의 모든 인스턴스를 생성합니다. 이 팩터리는 다음 용도로 사용됩니다.

문제 세부 정보 응답을 사용자 지정하려면 Program.cs에서 ProblemDetailsFactory의 사용자 지정 구현을 등록합니다.

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

ApiBehaviorOptions.ClientErrorMapping 사용

ClientErrorMapping 속성을 사용하여 ProblemDetails 응답의 내용을 구성합니다. 예를 들어 Program.cs 내의 다음 코드는 404 응답에 대해 Link 속성을 업데이트합니다.

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

추가 리소스

이 문서에서는 ASP.NET Core 웹 API를 사용하여 오류를 처리하고 사용자 지정하는 방법을 설명합니다.

개발자 예외 페이지

개발자 예외 페이지에는 서버 오류에 대한 자세한 스택 추적이 표시됩니다. 이것은 DeveloperExceptionPageMiddleware를 사용하여 HTTP 파이프라인에서 동기 및 비동기 예외를 캡처하고 오류 응답을 생성합니다. 예를 들어 예외를 throw하는 다음 컨트롤러 작업을 고려합니다.

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

개발자 예외 페이지에서 처리되지 않은 예외를 검색하면 다음 예제와 유사한 기본 일반 텍스트 응답이 생성됩니다.

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

클라이언트가 HTML 형식의 응답을 요청하는 경우 개발자 예외 페이지는 다음 예제와 유사한 응답을 생성합니다.

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

HTML 형식이 지정된 응답을 요청하려면 Accept HTTP 요청 헤더를 text/html에 설정합니다.

Warning

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

예외 처리기

비개발 환경에서는 예외 처리 미들웨어를 사용하여 오류 페이로드를 생성할 수 있습니다.

  1. Program.cs에서 UseExceptionHandler를 호출하여 예외 처리 미들웨어를 추가합니다.

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. /error 경로에 응답하도록 컨트롤러 작업을 구성합니다.

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

위의 HandleError 작업은 RFC 7807 호환 페이로드를 클라이언트에 보냅니다.

Warning

오류 처리기 작업 메서드를 HttpGet와 같은 HTTP 메서드 특성을 사용하여 표시하지 마세요. 명시적 동사는 일부 요청이 작업 메서드에 도달하지 못하도록 합니다.

Swagger/OpenAPI를 사용하는 웹 API의 경우 오류 처리기 작업을 [ApiExplorerSettings] 특성으로 표시하고 해당 IgnoreApi 속성을 true로 설정합니다. 이 특성 구성은 앱의 OpenAPI 사양에서 오류 처리기 작업을 제외합니다.

[ApiExplorerSettings(IgnoreApi = true)]

인증되지 않은 사용자에게 오류를 표시해야 하는 경우 메서드에 대한 익명 액세스를 허용합니다.

예외 처리 미들웨어를 개발 환경에서 사용하여 모든 환경에서 일관된 페이로드 형식을 생성할 수도 있습니다.

  1. Program.cs에서 환경별 예외 처리 미들웨어 인스턴스를 등록합니다.

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    위의 코드에서 미들웨어는 다음에 등록됩니다.

    • 개발 환경에서 /error-development의 경로.
    • 비개발 환경에서 /error의 경로입니다.

  2. 개발 및 비개발 경로 모두에 대한 컨트롤러 작업을 추가합니다.

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

예외를 사용하여 응답 수정

응답의 내용은 사용자 지정 예외 및 작업 필터를 사용하여 컨트롤러 외부에서 수정할 수 있습니다.

  1. HttpResponseException이라는 잘 알려진 예외 형식을 만듭니다.

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. HttpResponseExceptionFilter이라는 작업 필터를 만듭니다.

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    위의 필터는 최대 정수 값에서 10을 뺀 Order를 지정합니다. 이 Order를 통해 파이프라인의 끝에서 다른 필터를 실행할 수 있습니다.

  3. Program.cs에서 필터 컬렉션에 작업 필터를 추가합니다.

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

유효성 검사 실패 오류 응답

Web API 컨트롤러의 경우, 모델 유효성 검사에 실패하면 MVC는 ValidationProblemDetails 응답 형식으로 응답합니다. MVC는 InvalidModelStateResponseFactory의 결과를 사용하여 유효성 검사 실패에 대한 오류 응답을 생성합니다. 다음 예제에서는 기본 팩터리를 Program.cs에서 XML로 형식 지정 응답도 지원하는 구현으로 바꿉니다.

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

클라이언트 오류 응답

오류 결과는 400 이상의 HTTP 상태 코드를 가진 결과로 정의됩니다. 웹 API 컨트롤러의 경우, MVC는 ProblemDetails를 생성하도록 오류 결과를 변환합니다.

오류 응답은 다음 방법 중 하나로 구성할 수 있습니다.

  1. ProblemDetailsFactory 구현
  2. ApiBehaviorOptions.ClientErrorMapping 사용

도구 ProblemDetailsFactory

MVC는 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory를 사용하여 ProblemDetailsValidationProblemDetails의 모든 인스턴스를 생성합니다. 이 팩터리는 다음 용도로 사용됩니다.

문제 세부 정보 응답을 사용자 지정하려면 Program.cs에서 ProblemDetailsFactory의 사용자 지정 구현을 등록합니다.

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

ApiBehaviorOptions.ClientErrorMapping 사용

ClientErrorMapping 속성을 사용하여 ProblemDetails 응답의 내용을 구성합니다. 예를 들어 Program.cs 내의 다음 코드는 404 응답에 대해 Link 속성을 업데이트합니다.

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

예외를 처리하는 사용자 지정 미들웨어

예외 처리 미들웨어의 기본값은 대부분의 앱에서 잘 작동합니다. 특수한 예외 처리가 필요한 앱의 경우 예외 처리 미들웨어를 사용자 지정하는 것이 좋습니다.

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

ASP.NET Core는 처리되지 않은 예외가 발생할 때 표준화된 오류 페이로드를 생성하지 않습니다. 표준화된 ProblemDetails 응답을 클라이언트에 반환하는 것이 바람직한 시나리오의 경우 ProblemDetails 미들웨어를 사용하여 예외 및 404 응답을 ProblemDetails 페이로드에 매핑할 수 있습니다. 예외 처리 미들웨어를 사용하여 처리되지 않은 예외에 대한 ProblemDetails 페이로드를 반환할 수도 있습니다.

추가 리소스

이 문서에서는 ASP.NET Core 웹 API를 사용하여 오류를 처리하고 사용자 지정하는 방법을 설명합니다.

예제 코드 살펴보기 및 다운로드(다운로드 방법)

개발자 예외 페이지

개발자 예외 페이지는 서버 오류에 대한 자세한 스택 추적을 가져오는 데 유용한 도구입니다. 이것은 DeveloperExceptionPageMiddleware를 사용하여 HTTP 파이프라인에서 동기 및 비동기 예외를 캡처하고 오류 응답을 생성합니다. 설명하려면 다음 컨트롤러 작업을 고려하세요.

[HttpGet("{city}")]
public WeatherForecast Get(string city)
{
    if (!string.Equals(city?.TrimEnd(), "Redmond", StringComparison.OrdinalIgnoreCase))
    {
        throw new ArgumentException(
            $"We don't offer a weather forecast for {city}.", nameof(city));
    }
    
    return GetWeather().First();
}

다음 curl 명령을 실행하여 이전 작업을 테스트합니다.

curl -i https://localhost:5001/weatherforecast/chicago

클라이언트에서 HTML 형식의 출력을 요청하지 않는 경우 개발자 예외 페이지에 일반 텍스트 응답이 표시됩니다. 다음과 같은 출력이 표시됩니다.

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/plain
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:13:16 GMT

System.ArgumentException: We don't offer a weather forecast for chicago. (Parameter 'city')
   at WebApiSample.Controllers.WeatherForecastController.Get(String city) in C:\working_folder\aspnet\AspNetCore.Docs\aspnetcore\web-api\handle-errors\samples\3.x\Controllers\WeatherForecastController.cs:line 34
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Host: localhost:44312
User-Agent: curl/7.55.1

HTML 형식의 응답을 대신 표시하려면 Accept HTTP 요청 헤더를 text/html 미디어 유형으로 설정합니다. 예시:

curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago

HTTP 응답에서 다음 발췌 내용을 살펴보세요.

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:55:37 GMT

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

HTML 형식 응답은 curl과 같은 도구를 통해 테스트할 때 유용합니다.

Warning

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

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

예외 처리기

비개발 환경에서는 예외 처리 미들웨어를 사용하여 오류 페이로드를 생성할 수 있습니다.

  1. Startup.Configure에서 UseExceptionHandler를 호출하여 미들웨어를 사용합니다.

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  2. /error 경로에 응답하도록 컨트롤러 작업을 구성합니다.

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

위의 Error 작업은 RFC 7807 호환 페이로드를 클라이언트에 보냅니다.

예외 처리 미들웨어는 로컬 개발 환경에서 보다 자세한 콘텐츠 협상 출력을 제공할 수도 있습니다. 다음 단계를 사용하여 개발 및 프로덕션 환경에서 일관된 페이로드 형식을 생성합니다.

  1. Startup.Configure에서 환경별 예외 처리 미들웨어 인스턴스를 등록합니다.

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/error-local-development");
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    }
    

    위의 코드에서 미들웨어는 다음에 등록됩니다.

    • 개발 환경에서 /error-local-development의 경로.
    • 개발되지 않은 환경에서 /error의 경로.

  2. 컨트롤러 작업에 특성 라우팅 적용:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error-local-development")]
        public IActionResult ErrorLocalDevelopment(
            [FromServices] IWebHostEnvironment webHostEnvironment)
        {
            if (webHostEnvironment.EnvironmentName != "Development")
            {
                throw new InvalidOperationException(
                    "This shouldn't be invoked in non-development environments.");
            }
    
            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
    
            return Problem(
                detail: context.Error.StackTrace,
                title: context.Error.Message);
        }
    
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

    위의 코드는 ControllerBase.Problem을 호출하여 ProblemDetails 응답을 만듭니다.

예외를 사용하여 응답 수정

응답의 내용은 컨트롤러 외부에서 수정할 수 있습니다. ASP.NET 4.x Web API에서 이것을 수행하는 한 가지 방법은 HttpResponseException 형식을 사용하는 것입니다. ASP.NET Core에는 동일한 형식이 포함되지 않습니다. 다음 단계로 HttpResponseException에 대한 지원을 추가할 수 있습니다.

  1. HttpResponseException이라는 잘 알려진 예외 형식을 만듭니다.

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. HttpResponseExceptionFilter이라는 작업 필터를 만듭니다.

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order { get; } = int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException exception)
            {
                context.Result = new ObjectResult(exception.Value)
                {
                    StatusCode = exception.Status,
                };
                context.ExceptionHandled = true;
            }
        }
    }
    

    위의 필터는 최대 정수 값에서 10을 뺀 Order를 지정합니다. 이 Order를 통해 파이프라인의 끝에서 다른 필터를 실행할 수 있습니다.

  3. Startup.ConfigureServices에서 필터 컬렉션에 작업 필터를 추가합니다.

    services.AddControllers(options =>
        options.Filters.Add(new HttpResponseExceptionFilter()));
    

유효성 검사 실패 오류 응답

Web API 컨트롤러의 경우, 모델 유효성 검사에 실패하면 MVC는 ValidationProblemDetails 응답 형식으로 응답합니다. MVC는 InvalidModelStateResponseFactory의 결과를 사용하여 유효성 검사 실패에 대한 오류 응답을 생성합니다. 다음 예제에서는 팩터리를 사용하여 Startup.ConfigureServices에서 기본 응답 형식을 SerializableError로 변경합니다.

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var result = new BadRequestObjectResult(context.ModelState);

            // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames
            result.ContentTypes.Add(MediaTypeNames.Application.Json);
            result.ContentTypes.Add(MediaTypeNames.Application.Xml);

            return result;
        };
    });

클라이언트 오류 응답

오류 결과는 400 이상의 HTTP 상태 코드를 가진 결과로 정의됩니다. Web API 컨트롤러의 경우, MVC는 ProblemDetails의 결과로 오류 결과를 변환합니다.

오류 응답은 다음 방법 중 하나로 구성할 수 있습니다.

  1. ProblemDetailsFactory 구현
  2. ApiBehaviorOptions.ClientErrorMapping 사용

도구 ProblemDetailsFactory

MVC는 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory를 사용하여 ProblemDetailsValidationProblemDetails의 모든 인스턴스를 생성합니다. 이 팩터리는 다음 용도로 사용됩니다.

문제 세부 정보 응답을 사용자 지정하려면 Startup.ConfigureServices에서 ProblemDetailsFactory의 사용자 지정 구현을 등록합니다.

public void ConfigureServices(IServiceCollection serviceCollection)
{
    services.AddControllers();
    services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}

ApiBehaviorOptions.ClientErrorMapping 사용

ClientErrorMapping 속성을 사용하여 ProblemDetails 응답의 내용을 구성합니다. 예를 들어 Startup.ConfigureServices 내의 다음 코드는 404 응답에 대해 type 속성을 업데이트합니다.

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
        options.DisableImplicitFromServicesParameters = true;
    });

예외를 처리하는 사용자 지정 미들웨어

예외 처리 미들웨어의 기본값은 대부분의 앱에서 잘 작동합니다. 특수한 예외 처리가 필요한 앱의 경우 예외 처리 미들웨어를 사용자 지정하는 것이 좋습니다.

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

ASP.NET Core는 처리되지 않은 예외가 발생할 때 표준화된 오류 페이로드를 생성하지 않습니다. 표준화된 ProblemDetails 응답을 클라이언트에 반환하는 것이 바람직한 시나리오의 경우 ProblemDetails 미들웨어를 사용하여 예외 및 404 응답을 ProblemDetails 페이로드에 매핑할 수 있습니다. 예외 처리 미들웨어를 사용하여 처리되지 않은 예외에 대한 ProblemDetails 페이로드를 반환할 수도 있습니다.