Як повернути NotFound () IHttpActionResult із повідомленням про помилку або винятком?


98

Я повертаю NotFound IHttpActionResult, коли в моїй дії WebApi GET щось не знайдено. Разом із цією відповіддю я хочу надіслати власне повідомлення та / або повідомлення про виняток (якщо таке є). Тока ApiController«и NotFound()спосіб не забезпечує перевантаження , щоб надіслати електронний лист.

Чи є спосіб це зробити? або мені доведеться писати власний звичай IHttpActionResult?


Хочете повернути одне і те ж повідомлення для всіх результатів "Не знайдено"?
Микола Самтеладзе

@NikolaiSamteladze Ні, це може бути інше повідомлення залежно від ситуації.
Ajay Jadhav

Відповіді:


84

Вам потрібно написати власний результат дії, якщо ви хочете налаштувати форму повідомлення відповіді.

Ми хотіли надати найпоширеніші форми повідомлень відповідей з коробки для таких речей, як прості порожні 404-ті, але ми також хотіли зробити ці результати максимально простими; Однією з головних переваг використання результатів дій є те, що це значно полегшує ваш метод дії в модульному тестуванні. Чим більше властивостей ми надаємо результатам дії, тим більше речей потрібно враховувати вашому модульному тесту, щоб переконатися, що метод дії робить те, що ви очікували.

Я часто хочу також надати власне повідомлення, тому сміливо реєструйте помилку, щоб ми розглянули можливість підтримки результату цієї дії у майбутньому випуску: https://aspnetwebstack.codeplex.com/workitem/list/advanced

Однак одна приємна річ щодо результатів дій полягає в тому, що ви завжди можете досить легко написати власні, якщо хочете зробити щось дещо інше. Ось як ви можете це зробити у вашому випадку (за умови, що ви хочете отримати повідомлення про помилку в text / plain; якщо ви хочете 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 код виглядає приблизно так це і мій Controler в 'Get () дій виглядає наступним чином : «громадськість IHttpActionResult Get () {повернення CustomNotFoundResult (" Meessage до Return "); } 'Крім того, я зареєстрував помилку на CodePlex для розгляду цього питання в майбутньому випуску.
Ajay Jadhav

Я використовую ODataControllers, і мені довелося використовувати this.NotFound ("бла");
Джертер

1
Дуже хороший пост, але я просто хотів би порекомендувати проти підказки про спадщину. Моя команда давно вирішила зробити саме це, і дуже сильно роздула заняття, роблячи це. Нещодавно я переробив усе це на методи розширення і відійшов від ланцюжка успадкування. Я серйозно рекомендую людям ретельно обміркувати, коли їм слід користуватися таким спадщиною. Зазвичай склад набагато кращий, оскільки він набагато розв’язаний.
julealgon

6
Ця функціональність повинна була бути нестандартною. Включення необов’язкового параметра „ResponseBody” не повинно впливати на модульні тести.
Теодор Зографос,

230

Ось однорядковий рядок для повернення IHttpActionResult NotFound з простим повідомленням:

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

24
Люди повинні проголосувати за цю відповідь. Це приємно і просто!
Джесс

2
Майте на увазі, що це рішення не встановлює статус заголовка HTTP на "404 не знайдено".
Каспер Халвас Йенсен

4
@KasperHalvasJensen Код стану http з сервера - 404, вам потрібно щось більше?
Anthony F

4
@AnthonyF Ви маєте рацію. Я використовував Controller.Content (...). Треба було використовувати ApiController.Content (...) - Моя погана.
Каспер Халвас Йенсен,

Дякую, товаришу, це було саме те, що я шукав
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

Мені це подобається більше, ніж Content, оскільки він насправді повертає об’єкт, який я можу проаналізувати із властивістю Message, як і стандартний метод BadRequest.
user1568891

7

Ви можете використовувати властивість ReasonPhrase класу HttpResponseMessage

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

Дякую. Ну .. це має спрацювати, але тоді мені доведеться самостійно створювати HttpResponseException у кожній дії. Щоб коду було менше, я думав, чи можу я використовувати будь-які функції WebApi 2 (подібно до готових методів NotFount () , Ok () ) і передати йому повідомлення ReasonPhrase.
Ajay Jadhav

Ви можете створити власний метод розширення NotFound (виняток винятку), який викине правильний HttpResponseException
Дмитро Руденко

@DmytroRudenko: результати дій були представлені для покращення тестованості. Кинувши сюди HttpResponseException, ви це скомпрометуєте. Також тут ми не маємо жодних винятків, але операційна служба шукає відправлення повідомлення.
Кіран Чалла

Добре, якщо ви не хочете використовувати NUint для тестування, ви можете написати власну реалізацію NotFoundResult і переписати його ExecuteAsync для повернення даних ваших повідомлень. І повернути екземпляр цього класу в результаті виклику вашої дії.
Дмитро Руденко

1
Зверніть увагу, що тепер ви можете передавати код стану безпосередньо, наприклад, HttpResponseException (HttpStatusCode.NotFound)
Марк Совул

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>, як згадувалося, і вам не потрібно перетворювати свій 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екземпляр у тілі IExceptionHandlerкласу, щоб встановити ExceptionHandlerContext.Resultвластивість. Однак я також хотів встановити звичайReasonPhrase .

Я виявив, що a ResponseMessageResultможе обернути a 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" });
        }
    }
}

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 міститиме неконтрольований результат.

Я представляю тут кілька частин коду " кращий метод ApiController NotFound ", який забезпечить менш схильний до помилок метод, який не вимагає від розробників знання "кращого способу надсилання 404".

  • створити клас, що успадковує відApiController викликаєтьсяApiController
    • Я використовую цю техніку, щоб заборонити розробникам використовувати оригінальний клас
  • замінити його NotFoundметод щоб дозволити розробникам використовувати перший доступний api
  • якщо ви хочете перешкодити цьому, позначте це як [Obsolete("Use overload instead")]
  • додати додатковий protected NotFoundResult NotFound(string message) яку ви хочете заохотити
  • проблема: результат не підтримує відповідь тілом. рішення: успадкування та використання NegotiatedContentResult. див. доданий кращий клас NotFoundResult .
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.