Как вернуть NotFound() IHttpActionResult сообщение об ошибке или исключение?

Я возвращаю NotFound IHttpActionResult, когда что-то не найдено в моем действии WebApi GET. Наряду с этим ответом я хочу отправить специальное сообщение и/или сообщение об исключении (если есть). Текущий метод ApiController NotFound() не обеспечивает перегрузку для передачи сообщения.

Есть ли способ сделать это? или мне придется написать свой собственный IHttpActionResult?

Ответ 1

Вам нужно будет написать свой собственный результат действия, если вы хотите настроить форму сообщения ответа.

Мы хотели предоставить наиболее распространенные формы сообщений ответа из коробки для таких вещей, как простые пустые 404, но мы также хотели, чтобы эти результаты были максимально простыми; одним из главных преимуществ использования результатов действия является то, что он значительно упрощает ваш метод действий unit test. Чем больше свойств мы приводим в действие, тем больше вещей, которые должен учитывать ваш unit test, чтобы убедиться, что метод действия делает то, что вы ожидаете.

Я часто хочу, чтобы у вас была возможность создать собственное сообщение, поэтому не стесняйтесь регистрировать ошибку для нас, чтобы рассмотреть возможность поддержки этого результата в будущей версии: 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), вы также можете устранить "это". (что, к сожалению, требуется при вызове метода расширения):

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.");
    }
}

Ответ 2

Здесь один лайнер для возврата IHttpActionResult NotFound с простым сообщением:

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

Ответ 3

Вы можете использовать ResponseMessageResult, если хотите:

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

Да, если вам нужны гораздо более короткие версии, то, я думаю, вам нужно реализовать свой пользовательский результат действия.

Ответ 4

Вы можете использовать свойство ReasonPhrase класса HttpResponseMessage

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

Ответ 5

Я решил его просто получить из 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);
    }
}

Ответ 6

Вы можете создать пользовательский результат согласованного контента, как предположил 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);
    }
}

Ответ 7

Если вы наследуете базу NegotitatedContentResult<T>, как уже упоминалось, и вам не нужно преобразовывать ваш 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>, чтобы увидеть все, что он делает. Это не так много.

Ответ 8

Мне нужно было создать экземпляр IHttpActionResult в теле класса IExceptionHandler, чтобы установите свойство ExceptionHandlerContext.Result. Однако я также хотел установить пользовательский ReasonPhrase.

Я обнаружил, что ResponseMessageResult может обернуть HttpResponseMessage (что позволяет легко установить ReasonPhrase).

Пример:

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

Ответ 9

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