ASP.NET Web API 예외 처리
- 본 번역문서의 원문은 Exception Handling in ASP.NET Web API www.asp.net 입니다.
- 본 번역문서는 ASP.NET Web API 예외 처리 기법 www.taeyo.net 에서도 함께 제공됩니다.
본 문서에서는 ASP.NET Web API의 오류 및 예외 처리에 관해서 살펴봅니다.
HttpResponseException
Web API의 컨트롤러에서 처리되지 않은 예외가 발생하면 어떤 일이 벌어질까요? 대부분의 예외는 기본적으로 내부 서버 오류를 뜻하는 상태 코드 500 HTTP 응답으로 변환됩니다.
그러나, HttpResponseException 형식은 별개로 다뤄집니다. 이 예외 형식은 생성자에 지정한 HTTP 상태 코드를 반환합니다. 가령, 다음 메서드는 id 매개변수가 유효하지 않은 경우, HTTP 상태 코드 404, 즉 찾을 수 없음을 반환합니다.
public Product GetProduct(int id) { Product item = repository.Get(id); if (item == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return item; }
보다 세밀하게 응답을 제어하기 위해서, 응답 메시지 자체를 생성한 다음 HttpResponseException에 담을 수도 있습니다:
public Product GetProduct(int id) { Product item = repository.Get(id); if (item == null) { var resp = new HttpResponseMessage(HttpStatusCode.NotFound) { Content = new StringContent(string.Format("No product with ID = {0}", id)), ReasonPhrase = "Product ID Not Found" } throw new HttpResponseException(resp); } return item; }
예외 필터
다른 방법으로 예외 필터(Exception Filter)를 작성해서 Web API가 예외를 처리하는 방식을 직접 지정할 수도 있습니다. 예외 필터는 컨트롤러 메서드에서 HttpResponseException을 제외한 모든 유형의 처리되지 않은 예외가 던져지면 실행됩니다. HttpResponseException 형식은 특별하게 취급되는데, 그 이유는 애초에 이 예외가 HTTP 응답 반환을 위해 설계되었기 때문입니다.
예외 필터를 작성하려면 System.Web.Http.Filters.IExceptionFilter 인터페이스를 구현해야 합니다. 예외 필터를 구현하는 가장 간단한 방법은 System.Web.Http.Filters.ExceptionFilterAttribute 클래스를 상속 받아서 OnException 메서드를 재정의 하는 것입니다.
다음은 NotImplementedException 예외를 HTTP 상태 코드 501, 구현되지 않음으로 변환해주는 필터의 예제입니다:
namespace ProductStore.Filters { using System; using System.Net; using System.Net.Http; using System.Web.Http.Filters; public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute { public override void OnException(HttpActionExecutedContext context) { if (context.Exception is NotImplementedException) { context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented); } } } }
여기서 HttpActionExecutedContext 개체의 Response 속성은 클라이언트로 전송될 HTTP 응답 메시지를 담고 있습니다.
예외 필터 등록하기
Web API 예외 필터를 등록할 수 있는 방법은 다음과 같습니다:
- 액션에 등록
- 컨트롤러에 등록
- 전역 등록
특정 액션에 예외 필터를 적용하려면 필터를 액션에 어트리뷰트로 추가합니다:
public class ProductsController : ApiController { [NotImplExceptionFilter] public Contact GetContact(int id) { throw new NotImplementedException("This method is not implemented"); } }
컨트롤러에 존재하는 모든 액션에 필터를 적용하려면 필터를 컨트롤러 클래스에 어트리뷰트로 추가합니다:
[NotImplExceptionFilter] public class ProductsController : ApiController { // ... }
모든 Web API 컨트롤러에 전역으로 필터를 적용하려면 GlobalConfiguration.Configuration.Filters 컬렉션에 예외 필터의 인스턴스를 추가합니다. 이 컬렉션에 존재하는 예외 필터들은 모든 Web API 컨트롤러 액션에 적용됩니다.
GlobalConfiguration.Configuration.Filters.Add(new ProductStore.NotImplExceptionFilterAttribute());
만약, 프로젝트를 "ASP.NET MVC 4 웹 응용 프로그램" 프로젝트 템플릿을 선택해서 생성했다면, 다음과 같이 App_Start 폴더에 위치해 있는 WebApiConfig
클래스에 Web API 구성 코드를 추가하면 됩니다:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Filters.Add(new ProductStore.NotImplExceptionFilterAttribute()); // 다른 구성 코드들... } }
HttpError
HttpError 개체는 응답 본문에 오류 정보를 반환할 수 있는 일관된 방법을 제공해줍니다. 가령, 다음 예제는 HttpError 개체를 이용해서 HTTP 상태 코드 404, 찾을 수 없음을 응답 본문에 반환하는 방법을 보여줍니다:
public HttpResponseMessage GetProduct(int id) { Product item = repository.Get(id); if (item == null) { var message = string.Format("Product with id = {0} not found", id); HttpError err = new HttpError(message); return Request.CreateResponse(HttpStatusCode.NotFound, err); } else { return Request.CreateResponse(HttpStatusCode.OK, item); } }
이 예제의 메서드는 조회에 성공하면 제품 자체를 HTTP 응답으로 반환합니다. 그러나, 요청된 제품을 찾을 수 없다면 HTTP 응답의 응답 본문에 HttpError가 포함될 것입니다. 그리고, 그 응답은 아마도 다음과 비슷한 형태를 갖고 있을 것입니다:
HTTP/1.1 404 Not Found Content-Type: application/json; charset=utf-8 Date: Thu, 09 Aug 2012 23:27:18 GMT Content-Length: 51 { "Message": "Product with id = 12 not found" }
이 때, HttpError가 JSON 포멧으로 직렬화 된 상태라는 점에 주목하시기 바랍니다. HttpError를 사용할 때 얻을 수 있는 이점 중 하나가 바로 이것인데, 다른 모든 강력한 형식의 모델과 동일하게 내용 협상 (Content-Negotiation) 및 직렬화 처리가 수행된다는 점입니다.
또는 HttpError 개체를 직접 생성하는 대신, CreateErrorResponse 메서드를 사용할 수도 있습니다:
public HttpResponseMessage GetProduct(int id) { Product item = repository.Get(id); if (item == null) { var message = string.Format("Product with id = {0} not found", id); return Request.CreateErrorResponse(HttpStatusCode.NotFound, message); } else { return Request.CreateResponse(HttpStatusCode.OK, item); } }
이 CreateErrorResponse 메서드는 System.Net.Http.HttpRequestMessageExtensions 클래스에 정의되어 있는 확장 메서드입니다. 내부적으로 CreateErrorResponse 메서드는 HttpError의 인스턴스를 생성한 다음, 그 HttpError 개체를 포함하는 HttpResponseMessage 개체를 생성해줍니다.
HttpError 및 모델 유효성 검사
모델의 유효성 검사를 위해서 모델의 상태를 CreateErrorResponse에 전달해서 유효성 검사 오류 내용들을 응답에 포함시킬 수도 있습니다:
public HttpResponseMessage PostProduct(Product item) { if (!ModelState.IsValid) { return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState); } // 구현 생략... }
이 예제는 다음과 같은 응답 형태를 갖게 될 것입니다:
HTTP/1.1 400 Bad Request Content-Type: application/json; charset=utf-8 Content-Length: 320 { "Message": "The request is invalid.", "ModelState": { "item": [ "Required property 'Name' not found in JSON. Path '', line 1, position 14." ], "item.Name": [ "The Name field is required." ], "item.Price": [ "The field Price must be between 0 and 999." ] } }
모델 유효성 검사에 대한 더 자세한 정보는 Model Validation in ASP.NET Web API를 참고하시기 바랍니다.
HttpError에 사용자 정의 키-값 쌍 추가하기
지금까지 살펴본 HttpError 클래스는 키-값 구조를 가진 컬렉션(Dictionary<string, object>에서 파생된)일 뿐입니다. 따라서, 얼마든지 자신이 원하는 키-값 쌍을 추가할 수 있습니다:
public HttpResponseMessage GetProduct(int id) { Product item = repository.Get(id); if (item == null) { var message = string.Format("Product with id = {0} not found", id); var err = new HttpError(message); err["error_sub_code"] = 42; return Request.CreateErrorResponse(HttpStatusCode.NotFound, err); } else { return Request.CreateResponse(HttpStatusCode.OK, item); } }
HttpError와 HttpResponseException을 동시에 사용하기
직전 예제는 액션에서는 HttpResponseMessage를 반환하고 있지만 HttpResponseException을 사용해서 HttpError를 반환할 수도 있습니다. 이 방식을 사용하면 작업을 성공한 일반적인 경우에는 강력한 형식의 모델을 반환하고, 오류가 발생한 경우에는 지금까지 살펴본 것처럼 HttpError를 반환할 수도 있습니다:
public Product GetProduct(int id) { Product item = repository.Get(id); if (item == null) { var message = string.Format("Product with id = {0} not found", id); throw new HttpResponseException( Request.CreateErrorResponse(HttpStatusCode.NotFound, message)); } else { return item; } }
- 여러분의 첫 번째 ASP.NET Web API (C#) 2013-05-27 20:30
- CRUD 작업을 지원하는 Web API 작성하기 2013-06-14 20:30
- ASP.NET 웹폼에서 Web API 사용하기 2013-06-25 20:30
- ASP.NET Web API와 라우팅 2013-07-10 20:30
- ASP.NET Web API 도움말 페이지 작성하기 2013-08-14 15:43
- .NET 클라이언트에서 Web API 호출하기 (C#) 2013-08-21 16:26
- WPF 응용 프로그램에서 Web API 호출하기 (C#) 2013-08-28 22:41
- HttpClient 메시지 처리기 2013-09-04 17:16
- ASP.NET Web API 예외 처리 2013-09-11 21:23
- HTML 폼 데이터 전송하기 - 파트 1 2013-09-18 21:23
- HTML 폼 데이터 전송하기 - 파트 2 2013-09-25 01:11
- ASP.NET Web API와 HTTP 쿠키 2013-10-02 09:00
- 자체-호스트(Self-Host) Web API (C#) 2013-10-09 15:59
- OWIN을 이용한 ASP.NET Web API 자체 호스트(Self-Host) 2014-01-17 08:00
- 보안: ASP.NET Web API의 인증(Authentication)과 권한(Authorization) 2014-01-20 08:00
- 보안: 기본 인증 2014-01-22 08:00
- 보안: ASP.NET Web API와 개별 사용자 계정 2014-01-24 08:00
- 보안: 폼 인증 2014-01-27 08:00
- 보안: 통합 Windows 인증 2014-01-29 08:00
- 보안: 크로스 사이트 요청 위조(Cross-Site Request Forgery) 공격 방지하기 2014-02-03 08:00
- 보안: Web API에서 SSL 사용하기 2014-02-05 08:00
- 보안: 외부 인증 서비스 (C#) 2014-02-07 08:00
- 보안: ASP.NET Web API 교차-원본 요청(Cross-Origin Request) 활성화하기 2014-02-10 08:00