validationmessagefor validationmessage validate net mvc example asp c# css validation asp.net-web-api asp.net-mvc-5

c# - validationmessage - Validación del lado del servidor con.AP WebAPI



validationmessage mvc example (6)

Has probado

return BadRequest("This Email Already Exists!");

otra versión de BadRequest en lugar de lanzar una excepción?

Por razones de argumentos, digamos que estoy en Crear vista. Si dejé todos los cuadros de texto vacíos y presioné Enviar, obtendría el mismo formulario pero con los mensajes de validación debajo de cada cuadro de texto que se requería, y eso se hizo por validación del lado del cliente. Ahora, cuando eso sucede, cada cuadro de texto está decorado con un nombre de clase llamado input-validation-error , y si me gusta, puedo hacer que el cuadro se vuelva rojo para que se destaque más para el usuario.

Pero ahora, digamos que uno de los cuadros de texto requiere una dirección de correo electrónico. Las direcciones de correo electrónico son únicas, así que en mi controlador webapi tengo esto:

// POST: api/ControllerName [ResponseType(typeof(TestObject))] public IHttpActionResult PostTestObject(TestObject testObject) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if ( db.TestObjects.Any( x => x.Email.Equals(testObject.Email, StringComparison.CurrentCultureIgnoreCase) && x.ID != testObject.ID)) { ModelState.AddModelError("Email", "This Email Already Exists!"); return BadRequest(ModelState); } db.TestObjects.Add(testObject); db.SaveChanges(); return CreatedAtRoute("DefaultApi", new { id = testObject.ID }, testObject); }

En mi Crear vista, tengo esto para mostrar ese mensaje de excepción:

.error(function (jqXHR, textStatus, errorThrown) { var status = capitalizeFirstLetter(textStatus); var error = $.parseJSON(jqXHR.responseText); toastr.error(status + " - " + error.exceptionMessage); });

Esto muestra el mensaje de excepción en una notificación toastr. Pero no da al cuadro de texto del correo un nombre de clase de input-validation-error , y me gustaría que se muestre el cuadro de texto en rojo.

¿Hay alguna forma en los métodos de controlador de WebApi de devolver algo que agregue esa clase a ese cuadro de texto? Sé que en los controladores regulares .Net podría hacer

ModelState.AddModelError("Email", "This email already exists!") return View(testObject);

Eso devolvería la vista con ese cuadro de texto que tiene el nombre de la clase css.

Cualquier ayuda es apreciada.

Basado en la respuesta de Nkosi a continuación:

Cuando console.log(JSON.stringify(error));

La respuesta es esta:

{"$id":"1","message":"The request is invalid.","modelState": {"$id":"2","email":["This Email Already Exists!"]}}

De acuerdo, entonces he cambiado el formato para que se ajuste a la respuesta JSON, y también he cambiado la línea de var id = "#" + key.replace(''$'', ''''); a var id = "#" + key.replace(''$'', '''');

Ahora estoy recibiendo un error en valmsg.text(value.join()); diciendo que Object doesn''t support property or method ''join'' ... así que lo consolé y es 2 ... not "This Email Already Exists!"

ACTUALIZAR

.error(function (jqXHR, textStatus, errorThrown) { var error = jqXHR.responseJSON; console.log(JSON.stringify(error)); var message = error.message; var modelState = error.modelState; $.each(modelState, function (key, value) { var id = "#" + key.replace(''$'', ''''); var input = $(id); console.log(id); // result is #id if (input) { // if element exists input.addClass(''input-validation-error''); } //get validation message var valmsg = $("[data-valmsg-for=''" + key + "'']"); if (valmsg) { valmsg.text(value.join()); // Object doesn''t support property or method ''join'' valmsg.removeClass("field-validation-valid"); valmsg.addClass("field-validation-error"); }


Cuando su vista / html llama al método Web API, entonces la API Web básicamente no tiene idea de que su página o cuadros de entrada existen. Simplemente obtiene algo de entrada y devuelve algo de salida. En este caso, debido a que ha lanzado una excepción en la API web, la respuesta http es un código de error 500 para un "error interno del servidor". Esto provocaría que se ejecute el controlador de error xhr. No es ideal porque algo más haya salido mal (base de datos inactiva, conexión del cliente caída, etc.) mostrará el error de validación del correo electrónico. Una mejor idea sería devolver una respuesta más informativa dando resultados de validación en cada campo, pero luego necesita un tipo de respuesta con un poco más en él que un TestObject, algo así como un resultado donde el resultado tiene algunos campos para errores de validación.

Como solución rápida, probablemente desee utilizar alguna biblioteca de interfaz de usuario frontal para agregar manualmente esa clase al campo. Ejemplo en JQuery, justo antes / después de su línea de toastr:

$(''#emailfield'').addClass(''input-validation-error'')


Debería agregar la clase input-validation-error en su función .error al cuadro de texto o control deseado.

registrar la siguiente secuencia de comandos para validar

$(".selector").validate({ highlight: function(element, errorClass) { // Override the default behavior here } });


Si regresas en tu controlador:

ModelState.AddModelError("Email", "This Email Already Exists!"); return BadRequest(ModelState);

Luego, el json devuelto debería ser:

{"Email":["This Email Already Exists!"]}

En la salida de HTML en su vista, debe tener una entrada para la cual tendrá el atributo de nombre establecido en Correo electrónico:

<input name="Email" type="text" />

De forma similar, todas las demás claves del error JSON tendrán controles de formulario coincidentes con los atributos de nombre que coincidan con esas claves.

Entonces, en su función de error, puede recorrer las teclas y aplicar la clase CSS adecuada:

.error(function (jqXHR, textStatus, errorThrown) { var error = $.parseJSON(jqXHR.responseText); $(error).each(function(i,e) { $(''[name="'' + e + ''"]'').addClass(''input-validation-error''); }); });


ACTUALIZAR

basado en esta muestra de datos

{"$id":"1","message":"The request is invalid.","modelState": {"$id":"2","email":["This Email Already Exists!"]}}

El fragmento para resaltar los elementos inválidos se convertiría

var handleError = function (jqXHR, textStatus, errorThrown) { var error = jqXHR.responseJSON; var message = error.message; var modelState = error.modelState; //highlight invalid fields $.each(modelState, function (key, value) { var id = "#" + key; //construct id var input = $(id); //get the element if(input) { //if element exists input.addClass(''input-validation-error''); //update class } }); }

Original

El siguiente POC se utilizó para demostrar el problema original

WebApi

[HttpGet] [Route("testobject")] public IHttpActionResult TestObject() { ModelState.AddModelError("Email", "This Email Already Exists!"); return BadRequest(ModelState); }

Controlador MVC

[HttpGet, Route("")] public ActionResult Index() { var model = new TestVM(); return View(model); }

MVC View: índice

@model TestVM @{ ViewBag.Title = "Index"; } <div class="container"> <div class="form-group"> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(model => model.Email, new { data_bind = "value: Email", @class = "form-control" }) @Html.ValidationMessageFor(model => model.Email) </div> <button type="button" data-bind="click: testEmail" class="btn btn-success submit">Test</button> </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval", "~/bundles/knockout") <script type="text/javascript"> //Pay no attention to this. custom strongly typed helper for routes var url = ''@(Url.HttpRouteUrl<TestsApiController>(c => c.TestObject()))''; $(function () { /*using knockout for binding*/ function viewModel() { var self = this; //properties self.Email = ko.observable(@(Model.Email)); //methods self.testEmail = function () { $.ajax({ url: url, type: ''Get'', contentType: ''application/json'', dataType: ''json'', success: handleResponse, error: handleError, }); }; var handleError = function (jqXHR, textStatus, errorThrown) { var error = jqXHR.responseJSON; console.log(JSON.stringify(error)); var message = error.Message; var modelState = error.ModelState; //highlight invalid fields $.each(modelState, function (key, value) { var id = "#" + key; $(id).addClass(''input-validation-error''); //get validation message var valmsg = $("[data-valmsg-for=''" + key + "'']"); if (valmsg) { valmsg.text(value.join()); valmsg.removeClass("field-validation-valid"); valmsg.addClass("field-validation-error"); } }); } var handleResponse = function (data) { //No-op }; } var vm = new viewModel(); ko.applyBindings(vm); }); </script> }

Usando la prueba de concepto anterior basada en el ejemplo original en la pregunta, el modelo resultante devuelto se veía así.

{"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}}

Centrándome principalmente en manejar la respuesta de error devuelta, pude lograr el comportamiento deseado usando la siguiente estructura.

var handleError = function (jqXHR, textStatus, errorThrown) { var error = jqXHR.responseJSON; console.log(JSON.stringify(error)); //logs {"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}} var message = error.Message; var modelState = error.ModelState; //highlight invalid fields $.each(modelState, function (key, value) { var id = "#" + key; $(id).addClass(''input-validation-error''); //get validation message var valmsg = $("[data-valmsg-for=''" + key + "'']"); if (valmsg) { valmsg.text(value.join()); valmsg.removeClass("field-validation-valid"); valmsg.addClass("field-validation-error"); } }); }

Lo anterior cuando se ejecutó dio como resultado

Desde una vista que tenía el siguiente

<div class="container"> <div class="form-group"> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(model => model.Email, new { data_bind = "value: Email", @class = "form-control" }) @Html.ValidationMessageFor(model => model.Email) </div> <button type="button" data-bind="click: testEmail" class="btn btn-success submit">Test</button> </div>


Le recomendaría que use FluentValidation .

Verifique este gran artículo .

Entonces, usando este enfoque, podrá sacar su lógica de validación del controlador y hacer que las respuestas de error sigan el patrón.

Pero para agregar clases css en el lado del cliente, deberá reemplazar public List<string> Errors { get; set; } public List<string> Errors { get; set; } public List<string> Errors { get; set; } en ResponsePackeage con public Dictionary<string, string> Errors { get; set; } public Dictionary<string, string> Errors { get; set; } public Dictionary<string, string> Errors { get; set; } donde la clave será un nombre de propiedad y el valor será un mensaje de error relacionado.

¡Buena suerte!