Maneje la validación de ModelState en ASP.NET Web API

Me preguntaba cómo puedo lograr la validación del modelo con ASP.NET Web API. Tengo mi modelo así:

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

Luego tengo una acción de publicación en mi controlador de API:

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

¿Cómo agrego if(ModelState.IsValid) y luego manejo el mensaje de error para pasarlo al usuario?

Para la separación de la preocupación, le sugiero que use el filtro de acción para la validación del modelo, por lo que no necesita preocuparse demasiado sobre cómo hacer la validación en su controlador de 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); } } } 

Así, por ejemplo:

 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(); foreach (var state in ModelState) { foreach (var error in state.Value.Errors) { errors.Add(error.ErrorMessage); } } return Request.CreateResponse(HttpStatusCode.Forbidden, errors); } } 

Esto devolverá una respuesta como esta (asumiendo JSON, pero el mismo principio básico para 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."] 

Por supuesto, puede construir su lista / objeto de error de la forma que desee, por ejemplo, agregando nombres de campo, identificaciones de campo, etc.

Incluso si se trata de una llamada Ajax “de una sola vía” como una POST de una entidad nueva, aún debe devolver algo a la persona que llama, algo que indica si la solicitud fue exitosa o no. Imagine un sitio donde su usuario agregará información acerca de sí mismo a través de una solicitud AJAX POST. ¿Qué ocurre si la información que intentaron ingresar no es válida? ¿Cómo sabrán si su acción de Guardar fue exitosa o no?

La mejor forma de hacerlo es usar Buenos códigos de estado de HTTP como 200 OK y así sucesivamente. De esta forma, su JavaScript puede manejar fallas de forma adecuada utilizando las devoluciones de llamada correctas (error, éxito, etc.).

Aquí hay un buen tutorial sobre una versión más avanzada de este método, usando ActionFilter y jQuery: http://asp.net/web-api/videos/getting-started/custom-validation

Tal vez no sea lo que estabas buscando, pero tal vez sea bueno que alguien lo sepa:

Si está utilizando .net Web Api 2, puede hacer lo siguiente:

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

Dependiendo de los errores del modelo, obtienes este resultado:

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

Puede usar atributos del espacio de nombres System.ComponentModel.DataAnnotations para establecer reglas de validación. Consulte Validación del modelo: por Mike Wasson para más detalles.

Consulte también el video ASP.NET Web API, Parte 5: Validación personalizada – Jon Galloway

Otras referencias

  1. Dé un paseo en el lado del cliente con WebAPI y WebForms
  2. Cómo ASP.NET Web API vincula mensajes HTTP a modelos de dominio y cómo trabajar con formatos multimedia en Web API.
  3. Dominick Baier – Asegurar las API web de ASP.NET
  4. Enlazando la validación de AngularJS a la validación de la API web de ASP.NET
  5. Mostrar los errores de ModelState con AngularJS en ASP.NET MVC
  6. ¿Cómo presentar errores al cliente? AngularJS / WebApi ModelState
  7. Validación inyectada por dependencia en la API web

O bien, si está buscando una colección simple de errores para sus aplicaciones, aquí está mi implementación de esto:

 public override void OnActionExecuting(HttpActionContext actionContext) { var modelState = actionContext.ModelState; if (!modelState.IsValid) { var errors = new List(); 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); } } 

La respuesta del mensaje de error se verá así:

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

Aquí puede verificar para mostrar el error de estado del modelo uno por uno

  public HttpResponseMessage CertificateUpload(employeeModel emp) { if (!ModelState.IsValid) { string errordetails = ""; var errors = new List(); foreach (var state in ModelState) { foreach (var error in state.Value.Errors) { string p = error.ErrorMessage; errordetails = errordetails + error.ErrorMessage; } } Dictionary dict = new Dictionary(); dict.Add("error", errordetails); return Request.CreateResponse(HttpStatusCode.BadRequest, dict); } else { //do something } } 

}

DO#

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

También puede lanzar excepciones como se documenta aquí: http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx

Tenga en cuenta que para hacer lo que sugiere este artículo, recuerde incluir System.Net.Http

Tuve un problema al implementar el patrón de solución aceptado donde mi ModelStateFilter siempre devolvería false (y subsecuentemente un 400) para actionContext.ModelState.IsValid para ciertos objetos del modelo:

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

Solo acepto JSON, así que implementé una clase de cuaderno de modelo personalizado:

 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(posted); if (address != null) { // moar val here bindingContext.Model = address; return true; } return false; } } 

Que me registro directamente después de mi modelo a través de

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