¿Usar la validación de ASP.NET MVC con jquery ajax?

119

Tengo una acción simple de ASP.NET MVC como esta:

public ActionResult Edit(EditPostViewModel data)
{

}

El EditPostViewModeltener los atributos de validación de esta manera:

[Display(Name = "...", Description = "...")]
[StringLength(100, MinimumLength = 3, ErrorMessage = "...")]
[Required()]
public string Title { get; set; }

En la vista, estoy usando los siguientes ayudantes:

 @Html.LabelFor(Model => Model.EditPostViewModel.Title, true)

 @Html.TextBoxFor(Model => Model.EditPostViewModel.Title, 
                        new { @class = "tb1", @Style = "width:400px;" })

Si hago un envío en un formulario que este cuadro de texto se coloca en una validación se realizará primero en el cliente y luego en el servicio ( ModelState.IsValid).

Ahora tengo un par de preguntas:

  1. ¿Se puede usar esto con jQuery ajax submit en su lugar? Lo que estoy haciendo es simplemente eliminar el formulario y al hacer clic en el botón enviar, un javascript recopilará datos y luego ejecutará el archivo $.ajax.

  2. ¿Funcionará el lado del servidor ModelState.IsValid?

  3. ¿Cómo puedo reenviar el problema de validación al cliente y presentarlo como si estuviera usando build int validation ( @Html.ValidationSummary(true))?

Ejemplo de llamada Ajax:

function SendPost(actionPath) {
    $.ajax({
        url: actionPath,
        type: 'POST',
        dataType: 'json',
        data:
        {
            Text: $('#EditPostViewModel_Text').val(),
            Title: $('#EditPostViewModel_Title').val() 
        },
        success: function (data) {
            alert('success');
        },
        error: function () {
            alert('error');
        }
    });
}

Edición 1:

Incluido en la página:

<script src="/Scripts/jquery-1.7.1.min.js"></script>
<script src="/Scripts/jquery.validate.min.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js"></script>
Hiedra
fuente
Buena respuesta a continuación. Aquí hay una pregunta relacionada. La respuesta permite la validación del lado del cliente o del lado del servidor. Estoy enamorado del código JQuery que proporcionan. (No, no fue mi respuesta). Stackoverflow.com/questions/28987752/…
Aventura

Respuestas:

155

Lado del cliente

Usar la jQuery.validatebiblioteca debería ser bastante simple de configurar.

Especifique la siguiente configuración en su Web.configarchivo:

<appSettings>
    <add key="ClientValidationEnabled" value="true"/> 
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/> 
</appSettings>

Cuando construya su vista, definiría cosas como esta:

@Html.LabelFor(Model => Model.EditPostViewModel.Title, true)
@Html.TextBoxFor(Model => Model.EditPostViewModel.Title, 
                                new { @class = "tb1", @Style = "width:400px;" })
@Html.ValidationMessageFor(Model => Model.EditPostViewModel.Title)

NOTA: Estos deben definirse dentro de un elemento de formulario.

Entonces necesitaría incluir las siguientes bibliotecas:

<script src='@Url.Content("~/Scripts/jquery.validate.js")' type='text/javascript'></script>
<script src='@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")' type='text/javascript'></script>

Esto debería poder configurarlo para la validación del lado del cliente

Recursos

Lado del servidor

NOTA: Esto es solo para validación adicional del lado del servidor en la parte superior de la jQuery.validationbiblioteca.

Quizás algo como esto podría ayudar:

[ValidateAjax]
public JsonResult Edit(EditPostViewModel data)
{
    //Save data
    return Json(new { Success = true } );
}

Donde ValidateAjaxes un atributo definido como:

public class ValidateAjaxAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsAjaxRequest())
            return;

        var modelState = filterContext.Controller.ViewData.ModelState;
        if (!modelState.IsValid)
        {
            var errorModel = 
                    from x in modelState.Keys
                    where modelState[x].Errors.Count > 0
                    select new
                           {
                               key = x,
                               errors = modelState[x].Errors.
                                                      Select(y => y.ErrorMessage).
                                                      ToArray()
                           };
            filterContext.Result = new JsonResult()
                                       {
                                           Data = errorModel
                                       };
            filterContext.HttpContext.Response.StatusCode = 
                                                  (int) HttpStatusCode.BadRequest;
        }
    }
}

Lo que hace es devolver un objeto JSON que especifica todos los errores de su modelo.

La respuesta de ejemplo sería

[{
    "key":"Name",
    "errors":["The Name field is required."]
},
{
    "key":"Description",
    "errors":["The Description field is required."]
}]

Esto se devolverá a su error al manejar la devolución de llamada de la $.ajaxllamada

Puede recorrer los datos devueltos para configurar los mensajes de error según sea necesario en función de las claves devueltas (creo que algo como $('input[name="' + err.key + '"]')encontraría su elemento de entrada

Andrew Burgess
fuente
1
¡Gran respuesta, especialmente con el impresionante ValidateAjaxAttribute! ¡Gracias!
René
3
No entiendo por qué esta respuesta obtuvo tantos votos. No responde a la pregunta 1: ¿cómo hacer la validación del cliente al publicar con $ .ajax? Creo que la respuesta de @Shyju ayuda con eso.
Valentin
2
@Valentin: mi respuesta ayuda porque los datos también se validan en el lado del servidor. Los complementos de validación deben permitir la validación dinámica a medida que se completa el formulario, y cuando se envía el formulario (sin embargo, el OP quiere hacer eso), el servidor proporcionará la validación final, que es preferible de todos modos, ya que se puede omitir la validación del lado del cliente.
Andrew Burgess
8
Utilizo los intervalos de mensajes de validación de jQuery recorriendo los errores devueltos e insertando el mensaje de error en el intervalo correcto:for (var i = 0; i < modelStateErrors.length; i++) { $('span[data-valmsg-for="' + modelStateErrors[i].key + '"]').text(modelStateErrors[i].errors[0]); }
Ian
7
¡Esta respuesta es genial! Pero sigo pensando que el marco ASP.NET MVC debería proporcionar una forma integrada de hacerlo.
Zignd
40

Lo que debe hacer es serializar los datos de su formulario y enviarlos a la acción del controlador. ASP.NET MVC vinculará los datos del formulario al EditPostViewModelobjeto (su parámetro de método de acción), utilizando la función de vinculación del modelo MVC.

Puede validar su formulario en el lado del cliente y si todo está bien, envíe los datos al servidor. losvalid() método te resultará útil.

$(function () {

    $("#yourSubmitButtonID").click(function (e) {

        e.preventDefault();
        var _this = $(this);
        var _form = _this.closest("form");

        var isvalid = _form .valid();  // Tells whether the form is valid

        if (isvalid)
        {           
           $.post(_form.attr("action"), _form.serialize(), function (data) {
              //check the result and do whatever you want
           })
        }

    });

});
Shyju
fuente
1
¡Gracias! Intenté esto $ ("form #" + formId) .validate () pero dice que el formulario (que se encuentra) no tiene un validate ()?
Ivy
Vea Edit1 donde muestro que el script de validación está incluido en la página web. También veo que la validación se está ejecutando cuando se usa un botón de envío de tipo de entrada normal.
Ivy
Encontré el problema (eliminé Scripts.Render de la página maestra). ¿Pero todavía tiene problemas para devolver los errores de validación de ModelState al cliente? ¿Cómo se supone que lo haga? Por ejemplo, si el usuario ya no está conectado (ModelState.AddModelError ("CustomError", "texto de validación").
Ivy
1
debe incluir los archivos js jquery.validate y jquery.validate.unobtrusive en su página. ¿Sus entradas HTML tienen el atributo que buscan los complementos de validación?
Shyju
1
Ivy: su tipo de retorno (ActionResult) es una clase base de JsonResult. para que pueda devolver datos JSON. Lo que debe hacer es, si se trata de una llamada ajax (verifique Request.IsAjax), obtenga los errores de validación y cree un JSON y envíelo de vuelta al cliente. Verifique el json en el método de devolución de llamada de $ .post y muestre los mensajes de error.
Shyju
9

Aquí tienes una solución bastante simple:

En el controlador devolvemos nuestros errores así:

if (!ModelState.IsValid)
        {
            return Json(new { success = false, errors = ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage).ToList() }, JsonRequestBehavior.AllowGet);
        }

Aquí hay algunos de los guiones del cliente:

function displayValidationErrors(errors)
{
    var $ul = $('div.validation-summary-valid.text-danger > ul');

    $ul.empty();
    $.each(errors, function (idx, errorMessage) {
        $ul.append('<li>' + errorMessage + '</li>');
    });
}

Así es como lo manejamos a través de ajax:

$.ajax({
    cache: false,
    async: true,
    type: "POST",
    url: form.attr('action'),
    data: form.serialize(),
    success: function (data) {
        var isSuccessful = (data['success']);

        if (isSuccessful) {
            $('#partial-container-steps').html(data['view']);
            initializePage();
        }
        else {
            var errors = data['errors'];

            displayValidationErrors(errors);
        }
    }
});

Además, renderizo vistas parciales a través de ajax de la siguiente manera:

var view = this.RenderRazorViewToString(partialUrl, viewModel);
        return Json(new { success = true, view }, JsonRequestBehavior.AllowGet);

Método RenderRazorViewToString:

public string RenderRazorViewToString(string viewName, object model)
    {
        ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext,
                                                                     viewName);
            var viewContext = new ViewContext(ControllerContext, viewResult.View,
                                         ViewData, TempData, sw);
            viewResult.View.Render(viewContext, sw);
            viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
            return sw.GetStringBuilder().ToString();
        }
    }
Alex Herman
fuente
3
Su "GetModelStateErrors" puede simplificarse completamente convirtiéndolo en una declaración de una sola línea mucho más simple: return ModelState.Values.SelectMany (x => x.Errors) .Select (x => x.ErrorMessage) .ToList ();
Camilo Terevinto
1
¿Por qué no simplemente devolver un PartialViewrender a Ajax?
Sinjai
4

Se agregó un poco más de lógica a la solución proporcionada por @Andrew Burgess. Aquí está la solución completa:

Creó un filtro de acción para obtener errores para la solicitud ajax:

public class ValidateAjaxAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (!filterContext.HttpContext.Request.IsAjaxRequest())
                return;

            var modelState = filterContext.Controller.ViewData.ModelState;
            if (!modelState.IsValid)
            {
                var errorModel =
                        from x in modelState.Keys
                        where modelState[x].Errors.Count > 0
                        select new
                        {
                            key = x,
                            errors = modelState[x].Errors.
                                                          Select(y => y.ErrorMessage).
                                                          ToArray()
                        };
                filterContext.Result = new JsonResult()
                {
                    Data = errorModel
                };
                filterContext.HttpContext.Response.StatusCode =
                                                      (int)HttpStatusCode.BadRequest;
            }
        }
    }

Agregué el filtro a mi método de controlador como:

[HttpPost]
// this line is important
[ValidateAjax]
public ActionResult AddUpdateData(MyModel model)
{
    return Json(new { status = (result == 1 ? true : false), message = message }, JsonRequestBehavior.AllowGet);
}

Se agregó un script común para la validación de jquery:

function onAjaxFormError(data) {
    var form = this;
    var errorResponse = data.responseJSON;
    $.each(errorResponse, function (index, value) {
        // Element highlight
        var element = $(form).find('#' + value.key);
        element = element[0];
        highLightError(element, 'input-validation-error');

        // Error message
        var validationMessageElement = $('span[data-valmsg-for="' + value.key + '"]');
        validationMessageElement.removeClass('field-validation-valid');
        validationMessageElement.addClass('field-validation-error');
        validationMessageElement.text(value.errors[0]);
    });
}

$.validator.setDefaults({
            ignore: [],
            highlight: highLightError,
            unhighlight: unhighlightError
        });

var highLightError = function(element, errorClass) {
    element = $(element);
    element.addClass(errorClass);
}

var unhighLightError = function(element, errorClass) {
    element = $(element);
    element.removeClass(errorClass);
}

Finalmente agregué el método de error javascript a mi formulario Ajax Begin:

@model My.Model.MyModel
@using (Ajax.BeginForm("AddUpdateData", "Home", new AjaxOptions { HttpMethod = "POST", OnFailure="onAjaxFormError" }))
{
}
Manprit Singh Sahota
fuente
1

Puedes hacerlo de esta manera:

( Editar: considerando que estás esperando una respuesta jsoncon dataType: 'json')

.RED

public JsonResult Edit(EditPostViewModel data)
{
    if(ModelState.IsValid) 
    {
       // Save  
       return Json(new { Ok = true } );
    }

    return Json(new { Ok = false } );
}

JS:

success: function (data) {
    if (data.Ok) {
      alert('success');
    }
    else {
      alert('problem');
    }
},

Si lo necesita, también puedo explicar cómo hacerlo devolviendo un error 500 y obteniendo el error en el evento error (ajax). Pero en tu caso esta puede ser una opción

andres descalzo
fuente
1
Sé cómo hacer una solicitud regular de Jason, sin embargo, esto no me ayudará con la validación de las diferentes propiedades o incluso usará la validación ASP.NET MVC incorporada que solicité. Probablemente podría construir un objeto Jason complejo para explicar los errores de validación para cada propiedad, pero esto será mucho trabajo y espero que pueda reutilizar la funcionalidad incorporada de la validación de ASP.NET MVC para esto.
Ivy
1 - ¿Cómo ejecutar su función "SendPost" ?, y 2 - ¿Sus datos válidos en el cliente?
andres descalzo
Por favor, explique? ¿No recibes tu última publicación?
Ivy