30

Ho un'API Web su cui sto lavorando utilizzando il framework API Web MVC 4. Se c'è un'eccezione, sto attualmente lanciando una nuova HttpResponseException. vale a dire:Restituisce gli oggetti di errore personalizzati nell'API Web

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

Ciò restituisce un oggetto al client che è semplicemente {"message":"Invalid id"}

vorrei ottenere ulteriori controllo su questa risposta alle eccezioni restituendo un oggetto più dettagliata. Qualcosa come

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

Come potrei fare? Il modo migliore per serializzare il mio oggetto di errore e impostarlo nel messaggio di risposta?

Ho anche esaminato la ModelStateDictionary un po 'e sono venuti con questo po' di un "hack", ma non è ancora una potenza pulita:

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

modificare
sembra un personalizzato HttpError è quello che mi serve. Questo sembra fare il trucco, ora per rendere estensibile dal mio livello di business ...

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

risposta

9

penso che questo farà il trucco:

Creare una classe di eccezione personalizzata per lo strato di business:

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

Creare un metodo statico per generare un HttpError da un'istanza di MyException. Sto utilizzando la riflessione qui così posso aggiungere oggetti da MyException e sempre li ho restituiti w/o l'aggiornamento 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; 
    } 

Al momento ho un attributo personalizzato per un gestore generale eccezione. Tutte le eccezioni di tipo MyException verranno trattati qui:

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

giocherò in giro con questo un po 'di più e aggiornamento come trovo buchi in questo piano.

+3

Perché stai nascondendo la proprietà Messaggio? Non sarebbe più sicuro chiamare il tecnico di base e trasmettere il messaggio in questo modo? – Andy

2

Dai uno sguardo al seguente articolo. Ti aiuterà a ottenere il controllo delle eccezioni e dei messaggi di errore del tuo sito Web: Web Api, HttpError, and the Behavior of Exceptions

+1

Grazie. È simile a quello che sto facendo: creare un ExceptionFilterAttribute personalizzato – earthling

+3

Tale sito non è più disponibile – TravisO

39

Queste risposte sono molto più complicate di quanto debbano essere.

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

Questo è tutto ciò che serve. È anche bello e facile da testare:

[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() 
    }; 
} 
+0

Risposta eccellente, +1 per includere anche il test appropriato – xingyu

+0

Assicurati di aver utilizzato System.Net.Http; – Sal

+0

Questa è di gran lunga la migliore risposta. Più robusto e facile da implementare.Nella mia variante, nell'oggetto anonimo, ho aggiunto alcune proprietà con il messaggio di eccezione e altri suggerimenti utili per il debug. Grazie! –