web-dev-qa-db-fra.com

Renvoyer des objets d'erreur personnalisés dans l'API Web

J'ai une API Web sur laquelle je travaille en utilisant le cadre de l'API Web MVC 4. S'il y a une exception, je lance actuellement une nouvelle HttpResponseException. c'est à dire:

if (!Int32.TryParse(id, out userId))
    throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid id")); 

Cela renvoie un objet au client qui est simplement {"message":"Invalid id"}

Je voudrais contrôler davantage cette réponse aux exceptions en renvoyant un objet plus détaillé. Quelque chose comme

{
 "status":-1,
 "substatus":3,
 "message":"Could not find user"
 }

Comment pourrais-je procéder? Est le meilleur moyen de sérialiser mon objet d'erreur et de le définir dans le message de réponse?

J'ai également examiné un peu le ModelStateDictionary et j'ai trouvé ce petit "hack", mais ce n'est toujours pas une sortie propre:

var msd = new ModelStateDictionary();
msd.AddModelError("status", "-1");
msd.AddModelError("substatus", "3");
msd.AddModelError("message", "invalid stuff");
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, msd));

modifier
ressemble à un HttpError personnalisé dont j'ai besoin. Cela semble faire l'affaire, maintenant pour le rendre extensible à partir de ma couche métier ...

var error = new HttpError("invalid stuff") {{"status", -1}, {"substatus", 3}};
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, error));
31
earthling

Je pense que cela fera l'affaire:

Créez une classe d'exception personnalisée pour la couche de gestion:

 public class MyException: Exception
 {
    public ResponseStatus Status { get; private set; }
    public ResponseSubStatus SubStatus { get; private set; }
    public new string Message { get; private set; }

    public MyException()
    {}

    public MyException(ResponseStatus status, ResponseSubStatus subStatus, string message)
    {
        Status = status;
        SubStatus = subStatus;
        Message = message;
    }
 }

Créez une méthode statique pour générer un HttpError à partir d'une instance de MyException. J'utilise la réflexion ici pour pouvoir ajouter des propriétés à MyException et toujours les renvoyer sans mise à jour Create:

    public static HttpError Create<T>(MyException exception) where T:Exception
    {
        var properties = exception.GetType().GetProperties(BindingFlags.Instance 
                                                         | BindingFlags.Public 
                                                         | BindingFlags.DeclaredOnly);
        var error = new HttpError();
        foreach (var propertyInfo in properties)
        {
            error.Add(propertyInfo.Name, propertyInfo.GetValue(exception, null));
        }
        return error;
    }

J'ai actuellement un attribut personnalisé pour un gestionnaire d'exceptions générales. Toutes les exceptions de type MyException seront gérées ici:

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        var statusCode = HttpStatusCode.InternalServerError;

        if (context.Exception is MyException)
        {
            statusCode = HttpStatusCode.BadRequest;
            throw new HttpResponseException(context.Request.CreateErrorResponse(statusCode, HttpErrorHelper.Create(context.Exception)));
        }

        if (context.Exception is AuthenticationException)
            statusCode = HttpStatusCode.Forbidden;

        throw new HttpResponseException(context.Request.CreateErrorResponse(statusCode, context.Exception.Message));
    }
}

Je vais jouer avec cela un peu plus et mettre à jour car je trouve des trous dans ce plan.

9
earthling

Ces réponses sont bien plus compliquées qu'elles ne devraient l'être.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new HandleApiExceptionAttribute());
        // ...
    }
}

public class HandleApiExceptionAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        var request = context.ActionContext.Request;

        var response = new
        {
             //Properties go here...
        };

        context.Response = request.CreateResponse(HttpStatusCode.BadRequest, response);
    }
}

C'est tout ce dont vous avez besoin. C'est aussi un test unitaire agréable et facile:

[Test]
public async void OnException_ShouldBuildProperErrorResponse()
{
    var expected = new 
    {
         //Properties go here...
    };

    //Setup
    var target = new HandleApiExceptionAttribute()

    var contextMock = BuildContextMock();

    //Act
    target.OnException(contextMock);

    dynamic actual = await contextMock.Response.Content.ReadAsAsync<ExpandoObject>();

    Assert.AreEqual(expected.Aproperty, actual.Aproperty);
}

private HttpActionExecutedContext BuildContextMock()
{
    var requestMock = new HttpRequestMessage();
    requestMock.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    return new HttpActionExecutedContext()
    {
        ActionContext = new HttpActionContext
        {
            ControllerContext = new HttpControllerContext
            {
                Request = requestMock
            }

        },
        Exception = new Exception()
    };
}
41
FredM

Jetez un œil à l'article suivant. Il vous aidera à prendre le contrôle de vos exceptions et messages d'erreur de l'API Web: Web Api, HttpError et le comportement des exceptions

2
Andy Cohen