2012-07-27 13 views
85

Mi chiedevo come ottenere la convalida del modello con l'API Web ASP.NET. Ho il mio modello in questo modo:Gestire la convalida ModelState nell'API Web ASP.NET

public class Enquiry 
{ 
    [Key] 
    public int EnquiryId { get; set; } 
    [Required] 
    public DateTime EnquiryDate { get; set; } 
    [Required] 
    public string CustomerAccountNumber { get; set; } 
    [Required] 
    public string ContactName { get; set; } 
} 

Ho quindi avere un'azione Share mio controller API:

public void Post(Enquiry enquiry) 
{ 
    enquiry.EnquiryDate = DateTime.Now; 
    context.DaybookEnquiries.Add(enquiry); 
    context.SaveChanges(); 
} 

Come si aggiunge if(ModelState.IsValid) e quindi gestire il messaggio di errore di tramandare per l'utente?

risposta

155

Per la separazione di preoccupazione, vorrei suggerire di utilizzare il filtro d'azione per la validazione dei modelli, in modo che non è necessario interessarsi più di tanto come fare la convalida nel controllore api:

using System.Net; 
using System.Net.Http; 
using System.Web.Http.Controllers; 
using System.Web.Http.Filters; 

namespace System.Web.Http.Filters 
{ 
    public class ValidationActionFilter : ActionFilterAttribute 
    { 
     public override void OnActionExecuting(HttpActionContext actionContext) 
     { 
      var modelState = actionContext.ModelState; 

      if (!modelState.IsValid) 
       actionContext.Response = actionContext.Request 
        .CreateErrorResponse(HttpStatusCode.BadRequest, modelState); 
     } 
    } 
} 
+23

Gli spazi dei nomi necessari per questo sono 'System.Net.Http',' System.Net' 'System.Web.Http.Controllers' e' System.Web.Http.Filters'. –

+9

Esiste anche un'implementazione simile nella pagina ASP di Web ASP.NET ufficiale: http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web -api –

+0

Anche se non si mette [ValidationActionFilter] sopra web api, continua a chiamare il codice e mi dà una cattiva richiesta. – micronyks

25

In questo modo, ad esempio:

public HttpResponseMessage Post(Person person) 
{ 
    if (ModelState.IsValid) 
    { 
     PersonDB.Add(person); 
     return Request.CreateResponse(HttpStatusCode.Created, person); 
    } 
    else 
    { 
     // the code below should probably be refactored into a GetModelErrors 
     // method on your BaseApiController or something like that 

     var errors = new List<string>(); 
     foreach (var state in ModelState) 
     { 
      foreach (var error in state.Value.Errors) 
      { 
       errors.Add(error.ErrorMessage); 
      } 
     } 
     return Request.CreateResponse(HttpStatusCode.Forbidden, errors); 
    } 
} 

Ciò restituirà una risposta come questa (supponendo JSON, ma lo stesso principio di base per XML):

HTTP/1.1 400 Bad Request 
Content-Type: application/json; charset=utf-8 
(some headers removed here) 

["A value is required.","The field First is required.","Some custom errorm essage."] 

Ovviamente si può costruire il vostro errore oggetto/lista come preferisci, ad esempio aggiungendo nomi di campi, ID di campo ecc.

Anche se è una chiamata Ajax "a senso unico" come un POST di una nuova entità, dovresti comunque restituire qualcosa al chiamante - qualcosa che indica se la richiesta ha avuto successo. Immagina un sito in cui il tuo utente aggiungerà alcune informazioni su se stesso tramite una richiesta POST AJAX. Cosa succede se le informazioni che hanno tentato di inserire non sono valide - come faranno a sapere se la loro azione di salvataggio ha avuto esito positivo o no?

Il modo migliore per eseguire questa operazione è utilizzare Good Old HTTP Status Codes come 200 OK e così via. In questo modo il tuo JavaScript può gestire correttamente i guasti utilizzando i callback corretti (errore, successo, ecc.).

Ecco un bel tutorial su una versione più avanzata di questo metodo, utilizzando un ActionFilter e jQuery: http://asp.net/web-api/videos/getting-started/custom-validation

+0

Questo restituisce il mio oggetto 'query', non dice quali proprietà sono non valide? Quindi, se avessi lasciato 'CustomerAccountNumber' vuoto, dovrebbe dire che il messaggio di convalida predefinito (campo CusomterAccountNumber è richiesto ..) – CallumVass

+0

Modificato la mia risposta. –

+0

Vedo, quindi questo è il modo "corretto" di gestire la validazione del modello allora? Sembra un po 'complicato per me .. – CallumVass

16

forse non è quello che cercate, ma forse per qualcuno bello sapere:

Se si utilizza .NET Web Api 2 si potrebbe basta fare il seguito g:

if (!ModelState.IsValid) 
    return BadRequest(ModelState); 

A seconda degli errori del modello, si ottiene questo risultato:

{ 
    Message: "The request is invalid." 
    ModelState: { 
     model.PropertyA: [ 
      "The PropertyA field is required." 
     ], 
     model.PropertyB: [ 
      "The PropertyB field is required." 
     ] 
    } 
} 
+1

Bare a mente quando ha chiesto questa domanda Web API 1 è stato appena rilasciato, probabilmente è spostato su un sacco da allora :) – CallumVass

+0

Assicurati di contrassegnare le proprietà come facoltative, altrimenti riceverai un generico non utile "Si è verificato un errore". messaggio di errore. – bouke

9
1

Ho avuto un problema attuazione del accepted solution pattern dove il mio ModelStateFilter sarebbe sempre tornare false (e, successivamente, un 400) per actionContext.ModelState.IsValid per alcuni oggetti del modello:

public class ModelStateFilter : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     if (!actionContext.ModelState.IsValid) 
     { 
      actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest}; 
     } 
    } 
} 

Accetto solo JSON, quindi ho implementato una classe di raccoglitore modello personalizzata:

public class AddressModelBinder : System.Web.Http.ModelBinding.IModelBinder 
{ 
    public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext) 
    { 
     var posted = actionContext.Request.Content.ReadAsStringAsync().Result; 
     AddressDTO address = JsonConvert.DeserializeObject<AddressDTO>(posted); 
     if (address != null) 
     { 
      // moar val here 
      bindingContext.Model = address; 
      return true; 
     } 
     return false; 
    } 
} 

Quale mi iscrivo subito dopo il mio modello tramite

config.BindParameter(typeof(AddressDTO), new AddressModelBinder()); 
2

Qui è possibile controllare per mostrare l'errore modello di stato ad uno ad uno

public HttpResponseMessage CertificateUpload(employeeModel emp) 
    { 
     if (!ModelState.IsValid) 
     { 
      string errordetails = ""; 
      var errors = new List<string>(); 
      foreach (var state in ModelState) 
      { 
       foreach (var error in state.Value.Errors) 
       { 
        string p = error.ErrorMessage; 
        errordetails = errordetails + error.ErrorMessage; 

       } 
      } 
      Dictionary<string, object> dict = new Dictionary<string, object>(); 



      dict.Add("error", errordetails); 
      return Request.CreateResponse(HttpStatusCode.BadRequest, dict); 


     } 
     else 
     { 
     //do something 
     } 
     } 

}

2

C#

public class ValidateModelAttribute : ActionFilterAttribute 
    { 
     public override void OnActionExecuting(HttpActionContext actionContext) 
     { 
      if (actionContext.ModelState.IsValid == false) 
      { 
       actionContext.Response = actionContext.Request.CreateErrorResponse(
        HttpStatusCode.BadRequest, actionContext.ModelState); 
      } 
     } 
    } 

...

[ValidateModel] 
    public HttpResponseMessage Post([FromBody]AnyModel model) 
    { 

Javascript

$.ajax({ 
     type: "POST", 
     url: "/api/xxxxx", 
     async: 'false', 
     contentType: "application/json; charset=utf-8", 
     data: JSON.stringify(data), 
     error: function (xhr, status, err) { 
      if (xhr.status == 400) { 
       DisplayModelStateErrors(xhr.responseJSON.ModelState); 
      } 
     }, 
.... 


function DisplayModelStateErrors(modelState) { 
    var message = ""; 
    var propStrings = Object.keys(modelState); 

    $.each(propStrings, function (i, propString) { 
     var propErrors = modelState[propString]; 
     $.each(propErrors, function (j, propError) { 
      message += propError; 
     }); 
     message += "\n"; 
    }); 

    alert(message); 
}; 
3

O, se siete alla ricerca di semplice raccolta di errori per le applicazioni .. ecco la mia esecuzione del presente:

public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     var modelState = actionContext.ModelState; 

     if (!modelState.IsValid) 
     { 

      var errors = new List<string>(); 
      foreach (var state in modelState) 
      { 
       foreach (var error in state.Value.Errors) 
       { 
        errors.Add(error.ErrorMessage); 
       } 
      } 

      var response = new { errors = errors }; 

      actionContext.Response = actionContext.Request 
       .CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType); 
     } 
    } 

Messaggio di errore La risposta sarà simile a:

{ 
    "errors": [ 
    "Please enter a valid phone number (7+ more digits)", 
    "Please enter a valid e-mail address" 
    ] 
} 
Problemi correlati