web-dev-qa-db-fra.com

Comment renvoyer NotFound () IHttpActionResult avec un message d'erreur ou une exception?

Je retourne un NotFound IHttpActionResult, lorsque quelque chose ne se trouve pas dans mon action WebApi GET. Parallèlement à cette réponse, je souhaite envoyer un message personnalisé et/ou le message d'exception (le cas échéant). La méthode actuelle ApiControllerNotFound() ne fournit pas de surcharge pour transmettre un message.

Y a-t-il une manière de faire ça? ou je vais devoir écrire ma propre coutume IHttpActionResult?

91
Ajay Jadhav

Vous devez écrire votre propre résultat d'action si vous souhaitez personnaliser la forme du message de réponse.

Nous voulions fournir les formes de message de réponse les plus courantes prêtes à l'emploi pour des choses comme les simples 404 vides, mais nous voulions aussi garder ces résultats aussi simples que possible; L'un des principaux avantages de l'utilisation des résultats d'action est que cela rend votre méthode d'action beaucoup plus facile à tester à l'unité. Plus nous mettons de propriétés sur les résultats d'action, plus votre test unitaire doit prendre en compte pour s'assurer que la méthode d'action donne les résultats escomptés.

Je souhaite souvent pouvoir également fournir un message personnalisé. N'hésitez donc pas à consigner un bogue pour que nous envisagions de prendre en charge le résultat de cette action dans une version ultérieure: https://aspnetwebstack.codeplex.com/workitem/list/avancé

Une bonne chose à propos des résultats d’action, cependant, est que vous pouvez toujours écrire vous-même assez facilement si vous voulez faire quelque chose de légèrement différent. Voici comment vous pouvez le faire dans votre cas (en supposant que vous souhaitiez le message d'erreur dans text/plain; si vous voulez JSON, vous feriez quelque chose de légèrement différent avec le contenu):

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

Ensuite, dans votre méthode d'action, vous pouvez simplement faire quelque chose comme ceci:

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

Si vous avez utilisé une classe de base de contrôleur personnalisé (au lieu d’hériter directement d’ApiController), vous pouvez également éliminer le "ceci". partie (qui est malheureusement requise pour appeler une méthode d'extension):

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

Voici un one-liner pour renvoyer un IHttpActionResult NotFound avec un message simple:

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

Vous pouvez utiliser ResponseMessageResult si vous aimez:

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

oui, si vous avez besoin de versions beaucoup plus courtes, alors je suppose que vous devez implémenter votre résultat d'action personnalisé.

28
Kiran Challa

Vous pouvez utiliser la propriété ReasonPhrase de la classe HttpResponseMessage.

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

Je l'ai résolu en dérivant simplement de OkNegotiatedContentResult et en remplaçant le code HTTP dans le message de réponse obtenu. Cette classe vous permet de renvoyer le corps du contenu avec n’importe quel code de réponse 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);
    }
}
2
demeter

Vous pouvez créer un résultat de contenu négocié personnalisé comme suggéré par d3m3t3er. Cependant, je hériterais de. De même, si vous n'en avez besoin que pour renvoyer NotFound, vous n'avez pas besoin d'initialiser le statut http à partir du constructeur.

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
Andrei S

Si vous héritez de la base NegotitatedContentResult<T>, Comme indiqué, et que vous n'avez pas besoin de transformer votre content (par exemple, vous voulez simplement renvoyer une chaîne), alors vous n'avez pas besoin de remplacer la méthode ExecuteAsync.

Tout ce que vous avez à faire est de fournir une définition de type appropriée et un constructeur qui indique à la base le code d'état HTTP à renvoyer. Tout le reste fonctionne.

Voici des exemples pour NotFound et 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) { }
}

Et vous pouvez ensuite créer les méthodes d’extension correspondantes pour ApiController (ou le faire dans une classe de base si vous en avez une):

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

Et ensuite, ils fonctionnent exactement comme les méthodes intégrées. Vous pouvez appeler la NotFound() existante ou votre nouvelle NotFound(myErrorMessage) personnalisée.

Et bien sûr, vous pouvez vous débarrasser des types de chaînes "codés en dur" dans les définitions de types personnalisés et les laisser génériques si vous le souhaitez, mais alors vous pouvez devez vous soucier de la ExecuteAsync choses, en fonction de ce que votre <T> est réellement.

Vous pouvez regarder par-dessus le code source pour NegotiatedContentResult<T> Pour voir tout ce que cela fait. Il n'y a pas grand chose à faire.

1
sliderhouserules

J'avais besoin de créer une instance IHttpActionResult dans le corps d'une classe IExceptionHandler , afin de définir le ExceptionHandlerContext.Result propriété. Cependant, je souhaitais également définir un paramètre personnalisé ReasonPhrase.

J'ai trouvé qu'un ResponseMessageResult pourrait envelopper un HttpResponseMessage (ce qui permet de définir facilement ReasonPhrase).

Par exemple:

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