Pregunta ¿Cómo devuelvo NotFound () IHttpActionResult con un mensaje de error o una excepción?


Estoy devolviendo un NotFound IHttpActionResult, cuando algo no se encuentra en mi acción WebApi GET. Junto con esta respuesta, deseo enviar un mensaje personalizado y / o el mensaje de excepción (si corresponde). La corriente ApiControlleres NotFound() método no proporciona una sobrecarga para pasar un mensaje.

¿Hay alguna forma de hacer esto? o tendré que escribir mi propia costumbre IHttpActionResult?


73
2017-11-22 07:43


origen


Respuestas:


Tendrá que escribir su propio resultado de acción si desea personalizar la forma del mensaje de respuesta.

Queríamos proporcionar las formas más comunes de mensajes de respuesta para cosas como simples 404 vacíos, pero también queríamos mantener estos resultados lo más simple posible; Una de las principales ventajas del uso de los resultados de acción es que hace que su método de acción sea mucho más fácil para la prueba unitaria. Cuantas más propiedades ponemos en los resultados de acción, más cosas debe considerar su prueba de unidad para asegurarse de que el método de acción está haciendo lo que usted esperaría.

A menudo también quiero la posibilidad de proporcionar un mensaje personalizado, así que siéntase libre de registrar un error para que podamos considerar el respaldo de ese resultado de acción en una versión futura: https://aspnetwebstack.codeplex.com/workitem/list/advanced

Una cosa buena de los resultados de acción, sin embargo, es que siempre puedes escribir la tuya con bastante facilidad si quieres hacer algo ligeramente diferente. Así es como puede hacerlo en su caso (suponiendo que desea el mensaje de error en texto / simple, si desea JSON, haría algo ligeramente diferente con el contenido):

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

Luego, en su método de acción, puede hacer algo como esto:

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

Si utilizó una clase base de controlador personalizado (en lugar de heredar directamente de ApiController), también podría eliminar el "esto". parte (que lamentablemente se requiere al llamar a un método de extensión):

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

76
2017-11-22 18:29



Aquí hay una línea para devolver un IHttpActionResult NotFound con un mensaje simple:

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

165
2018-06-19 16:14



Podrías usar ResponseMessageResult Si te gusta:

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

Sí, si necesita versiones mucho más cortas, entonces creo que debe implementar su resultado de acción personalizado.


26
2017-11-22 14:35



Puede usar la propiedad ReasonPhrase de la clase HttpResponseMessage

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

7
2017-11-22 07:49



Lo resolví simplemente derivando de OkNegotiatedContentResult y anulando el código HTTP en el mensaje de respuesta resultante. Esta clase le permite devolver el cuerpo del contenido con cualquier código de respuesta 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
2018-06-06 13:55



Puede crear un resultado de contenido negociado personalizado como se sugirió d3m3t3er. Sin embargo, heredaría de. Además, si lo necesita solo para devolver NotFound, no necesita inicializar el estado de http del constructor.

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
2017-07-10 12:48



Si heredas de la base NegotitatedContentResult<T>, como se mencionó, y no necesita transformar su content (por ejemplo, solo quiere devolver una cadena), luego no necesita anular el ExecuteAsync método.

Todo lo que necesita hacer es proporcionar una definición de tipo apropiada y un constructor que le indique a la base qué código de estado HTTP debe devolver. Todo lo demás solo funciona

Aquí hay ejemplos para ambos NotFoundy 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) { }
}

Y luego puede crear los métodos de extensión correspondientes para ApiController (o hazlo en una clase base si tienes una):

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

Y luego funcionan igual que los métodos integrados. Puede llamar al existente NotFound() o puede llamar a su nueva costumbre NotFound(myErrorMessage).

Y, por supuesto, puede deshacerse de los tipos de cadena "codificados" en las definiciones de tipo personalizado y dejarlo genérico si lo desea, pero entonces usted mayo tiene que preocuparse por el ExecuteAsync cosas, dependiendo de lo que su <T> en realidad es.

Puedes mirar sobre el código fuente para NegotiatedContentResult<T> para ver todo lo que hace No hay mucho para eso.


1
2017-10-22 19:05



Necesitaba crear un IHttpActionResult instancia en el cuerpo de un IExceptionHandler clase, para establecer el ExceptionHandlerContext.Result propiedad. Sin embargo, también quería establecer una costumbre ReasonPhrase.

Encontré que un ResponseMessageResult podría envolver una HttpResponseMessage (que permite establecer fácilmente ReasonPhrase).

Por ejemplo:

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
2018-05-04 07:23