오류 메시지 또는 예외와 함께 NotFound () IHttpActionResult를 반환하려면 어떻게해야합니까?


98

IHttpActionResultWebApi GET 작업에서 무언가를 찾을 수없는 경우 NotFound를 반환하고 있습니다. 이 응답과 함께 사용자 지정 메시지 및 / 또는 예외 메시지 (있는 경우)를 보내려고합니다. 현재 ApiControllerNotFound()메서드는 메시지를 전달하기위한 오버로드를 제공하지 않습니다.

이 작업을 수행 할 방법이 있습니까? 아니면 내 사용자 정의를 작성해야 IHttpActionResult합니까?


모든 찾을 수 없음 결과에 대해 동일한 메시지를 반환 하시겠습니까?
Nikolai Samteladze 2013

@NikolaiSamteladze 아니요, 상황에 따라 메시지가 다를 수 있습니다.
Ajay Jadhav 2013

답변:


84

응답 메시지 모양을 사용자 지정하려면 고유 한 작업 결과를 작성해야합니다.

우리는 단순한 빈 404와 같은 것에 대해 가장 일반적인 응답 메시지 모양을 즉시 제공하고 싶었지만 이러한 결과를 가능한 한 단순하게 유지하고 싶었습니다. 작업 결과 사용의 주요 이점 중 하나는 작업 방법을 단위 테스트하기 훨씬 쉽게 만든다는 것입니다. 작업 결과에 더 많은 속성을 적용할수록 작업 메서드가 예상 한 작업을 수행하는지 확인하기 위해 단위 테스트에서 고려해야 할 사항이 많아집니다.

나는 종종 사용자 지정 메시지를 제공 할 수있는 기능을 원하므로 향후 릴리스에서 해당 작업 결과를 지원할 수 있도록 버그를 기록해도됩니다. https://aspnetwebstack.codeplex.com/workitem/list/advanced

하지만 액션 결과에 대한 한 가지 좋은 점은 약간 다른 작업을 수행하려는 경우 항상 매우 쉽게 작성할 수 있다는 것입니다. 다음은 귀하의 경우에 수행 할 수있는 방법입니다 (텍스트 / 일반 형식으로 오류 메시지를 원한다고 가정하고 JSON을 원하면 내용과 약간 다른 작업을 수행해야 함).

public class NotFoundTextPlainActionResult : IHttpActionResult
{
    public NotFoundTextPlainActionResult(string message, HttpRequestMessage request)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }

        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        Message = message;
        Request = request;
    }

    public string Message { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    public HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
        response.RequestMessage = Request;
        return response;
    }
}

public static class ApiControllerExtensions
{
    public static NotFoundTextPlainActionResult NotFound(this ApiController controller, string message)
    {
        return new NotFoundTextPlainActionResult(message, controller.Request);
    }
}

그런 다음 작업 방법에서 다음과 같이 할 수 있습니다.

public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        return this.NotFound("These are not the droids you're looking for.");
    }
}

ApiController에서 직접 상속하는 대신 사용자 지정 컨트롤러 기본 클래스를 사용한 경우 "this"를 제거 할 수도 있습니다. 부분 (안타깝게도 확장 메서드를 호출 할 때 필요함) :

public class CustomApiController : ApiController
{
    protected NotFoundTextPlainActionResult NotFound(string message)
    {
        return new NotFoundTextPlainActionResult(message, Request);
    }
}

public class TestController : CustomApiController
{
    public IHttpActionResult Get()
    {
        return NotFound("These are not the droids you're looking for.");
    }
}

1
'IHttpActionResult'와 똑같은 구현을 작성했지만 'NotFound'결과에 대해서는 구체적이지 않았습니다. 이것은 아마도 모든 'HttpStatusCodes'에서 작동합니다. 내 CustomActionResult 코드는 다음과 같습니다. 그리고 내 컨트롤러의 'Get ()'동작은 다음과 같습니다. 'public IHttpActionResult Get () {return CustomNotFoundResult ( "Meessage to Return."); } '또한 향후 릴리스에서이를 고려하기 위해 CodePlex에 버그 를 기록했습니다 .
Ajay Jadhav 2013

나는 ODataControllers를 사용했고 this.NotFound ( "blah");
Jerther 2014-12-05

1
아주 좋은 게시물이지만 상속 팁에 대해 추천하고 싶습니다. 우리 팀은 오래 전에 정확히 그렇게하기로 결정했고, 그렇게함으로써 수업이 많이 늘어났습니다. 나는 최근에 모든 것을 확장 메서드로 리팩토링하고 상속 체인에서 멀어졌습니다. 나는 사람들이 이런 상속을 사용해야 할 때 신중하게 고려할 것을 진지하게 권장합니다. 일반적으로 구성은 훨씬 더 분리되어 있기 때문에 훨씬 좋습니다.
julealgon 2015

6
이 기능은 즉시 사용 가능해야합니다. 선택적 "ResponseBody"매개 변수를 포함하는 것은 단위 테스트에 영향을주지 않습니다.
시어 도어 Zographos

230

다음은 간단한 메시지와 함께 IHttpActionResult NotFound를 반환하는 한 줄입니다.

return Content(HttpStatusCode.NotFound, "Foo does not exist.");

24
사람들은이 답변에 투표해야합니다. 멋지고 쉽습니다!
Jess

2
이 솔루션은 HTTP 헤더 상태를 "404 찾을 수 없음"으로 설정하지 않습니다.
Kasper Halvas Jensen 2011

4
@KasperHalvasJensen 서버의 http 상태 코드는 404입니다. 더 필요한 것이 있습니까?
Anthony F

4
@AnthonyF 당신이 맞습니다. Controller.Content (...)를 사용하고있었습니다. Shoud는 ApiController.Content (...)를 사용했습니다.
Kasper Halvas Jensen

덕분에이 내가 찾던 정확히 무엇이고, 짝
Kaptein Babbalas

28

원하는 ResponseMessageResult경우 사용할 수 있습니다 .

var myCustomMessage = "your custom message which would be sent as a content-negotiated response"; 
return ResponseMessage(
    Request.CreateResponse(
        HttpStatusCode.NotFound, 
        myCustomMessage
    )
);

예, 훨씬 더 짧은 버전이 필요한 경우 사용자 지정 작업 결과를 구현해야합니다.


이 방법은 깔끔해 보였습니다. 방금 사용자 지정 메시지를 다른 곳에 정의하고 반환 코드를 들여 쓰었습니다.
ozzy432836

표준 BadRequest 메서드와 마찬가지로 Message 속성으로 구문 분석 할 수있는 개체를 실제로 반환하기 때문에 Content보다 더 좋습니다.
user1568891

7

HttpResponseMessage 클래스의 ReasonPhrase 속성을 사용할 수 있습니다.

catch (Exception exception)
{
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
  {
    ReasonPhrase = exception.Message
  });
}

감사. 음 .. 이것은 작동하지만 모든 작업에서 HttpResponseException을 직접 작성해야합니다. 코드를 적게 유지하기 위해 WebApi 2 기능 (기성품 NotFount () , Ok () 메서드와 같은 )을 사용하고 ReasonPhrase 메시지를 전달할 수 있는지 생각했습니다.
Ajay Jadhav 2013

당신은 올바른 HttpResponseException가 발생합니다 자신의 확장 메서드 NOTFOUND (예외 예외), 만들 수 있습니다
드미트로 Rudenko

@DmytroRudenko : 테스트 가능성을 개선하기 위해 작업 결과가 도입되었습니다. 여기에 HttpResponseException을 던지면이를 손상시킬 수 있습니다. 또한 여기에는 예외가 없지만 OP는 메시지를 다시 보내고 있습니다.
Kiran Challa 2013

좋습니다. 테스트에 NUint를 사용하지 않으려면 자체 NotFoundResult 구현을 작성하고 메시지 데이터를 반환하기 위해 ExecuteAsync를 다시 작성할 수 있습니다. 그리고 액션 호출의 결과로이 클래스의 인스턴스를 반환합니다.
Dmytro Rudenko 2013

1
이제 상태 코드를 직접 전달할 수 있습니다. 예 : HttpResponseException (HttpStatusCode.NotFound)
Mark Sowul

3

d3m3t3er가 제안한대로 사용자 정의 협상 된 콘텐츠 결과를 만들 수 있습니다. 그러나 나는 상속 할 것입니다. 또한 NotFound 반환에만 필요한 경우 생성자에서 http 상태를 초기화 할 필요가 없습니다.

public class NotFoundNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
    public NotFoundNegotiatedContentResult(T content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller)
    {
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => task.Result, cancellationToken);
    }
}

2

OkNegotiatedContentResult결과 응답 메시지에서 HTTP 코드를 단순히 파생 시키고 재정 의하여 문제를 해결했습니다 . 이 클래스를 사용하면 HTTP 응답 코드와 함께 콘텐츠 본문을 반환 할 수 있습니다.

public class CustomNegotiatedContentResult<T> : OkNegotiatedContentResult<T>
{
    public HttpStatusCode HttpStatusCode;

    public CustomNegotiatedContentResult(
        HttpStatusCode httpStatusCode, T content, ApiController controller)
        : base(content, controller)
    {
        HttpStatusCode = httpStatusCode;
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => { 
                // override OK HTTP status code with our own
                task.Result.StatusCode = HttpStatusCode;
                return task.Result;
            },
            cancellationToken);
    }
}

1

NegotitatedContentResult<T>언급했듯이 base에서 상속 하고 변환 할 필요가없는 경우 content(예 : 문자열을 반환하려는 경우) ExecuteAsync메서드 를 재정의 할 필요가 없습니다 .

여러분이해야 할 일은 적절한 타입 정의와 반환 할 HTTP 상태 코드를베이스에 알려주는 생성자를 제공하는 것뿐입니다. 다른 모든 것은 작동합니다.

다음은 NotFound및 모두에 대한 예입니다 InternalServerError.

public class NotFoundNegotiatedContentResult : NegotiatedContentResult<string>
{
    public NotFoundNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller) { }
}

public class InternalServerErrorNegotiatedContentResult : NegotiatedContentResult<string>
{
    public InternalServerErrorNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.InternalServerError, content, controller) { }
}

그런 다음 다음에 대한 해당 확장 메서드를 만들 수 있습니다 ApiController(또는 기본 클래스가있는 경우이를 수행).

public static NotFoundNegotiatedContentResult NotFound(this ApiController controller, string message)
{
    return new NotFoundNegotiatedContentResult(message, controller);
}

public static InternalServerErrorNegotiatedContentResult InternalServerError(this ApiController controller, string message)
{
    return new InternalServerErrorNegotiatedContentResult(message, controller);
}

그런 다음 기본 제공 방법처럼 작동합니다. 기존 NotFound()을 호출하거나 새 사용자 정의를 호출 할 수 있습니다 NotFound(myErrorMessage).

그리고 물론, 사용자 정의 유형 정의에서 "하드 코딩 된"문자열 유형을 제거하고 원하는 경우 일반 상태로 둘 수 있지만 , 실제로 무엇인지에 따라 문제에 대해 걱정해야 할 수도 있습니다 .ExecuteAsync<T>

당신은을 통해 볼 수 소스 코드 에 대한 NegotiatedContentResult<T>이 수행 모두 볼 수 있습니다. 그다지 많지 않습니다.


1

IHttpActionResult본문에 인스턴스 를 만들어야했습니다 .IExceptionHandlerExceptionHandlerContext.Result속성 을 설정하기 위해 클래스 . 그러나 나는 또한 사용자 정의를 설정하고 싶었습니다 ReasonPhrase.

a ResponseMessageResult가 a 를 래핑 할 수 있다는 것을 발견 했습니다 HttpResponseMessage(ResonPhrase를 쉽게 설정할 수 있음).

예를 들면 :

public class MyExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        var ex = context.Exception as IRecordNotFoundException;
        if (ex != null)
        {
            context.Result = new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = $"{ex.EntityName} not found" });
        }
    }
}

0

Iknow PO가 메시지 텍스트로 요청했지만 404를 반환하는 또 다른 옵션은 메서드가 IHttpActionResult를 반환하고 StatusCode 함수를 사용하도록하는 것입니다.

    public async Task<IHttpActionResult> Get([FromUri]string id)
    {
       var item = await _service.GetItem(id);
       if(item == null)
       {
           StatusCode(HttpStatusCode.NotFound);
       }
       return Ok(item);
    }

0

여기에 대한 답변에는 약간의 개발자 스토리 문제가 없습니다. ApiController클래스는 여전히 노출되어NotFound() 개발자가 사용할 수 있다는 방법을. 이로 인해 일부 404 응답에 제어되지 않은 결과 본문이 포함됩니다.

여기 에서는 개발자가 "404를 보내는 더 나은 방법"을 알 필요없이 오류가 발생하기 쉬운 메서드를 제공하는 " 더 나은 ApiController NotFound 메서드 " 코드의 일부를 제시합니다 .

  • 호출 에서 상속ApiController 하는 클래스 만들기ApiController
    • 이 기술을 사용하여 개발자가 원래 클래스를 사용하지 못하도록합니다.
  • NotFound개발자가 사용 가능한 첫 번째 API를 사용할 수 있도록 메서드재정의합니다.
  • 이것을 막으려면 이것을 다음과 같이 표시하십시오. [Obsolete("Use overload instead")]
  • protected NotFoundResult NotFound(string message)장려하고 싶은 추가 추가
  • 문제 : 결과가 신체 응답을 지원하지 않습니다. 솔루션 : 상속 및 사용 NegotiatedContentResult. 첨부 된 더 나은 NotFoundResult 클래스를 참조하십시오 .
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.