Use a validação ASP.NET MVC com jquery ajax?

119

Eu tenho uma ação simples do ASP.NET MVC como esta:

public ActionResult Edit(EditPostViewModel data)
{

}

Eles EditPostViewModeltêm atributos de validação como este:

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

Na visão, estou usando os seguintes ajudantes:

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

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

Se eu fizer um envio em um formulário que esta caixa de texto seja colocada em uma validação será feita primeiro no cliente e depois no serviço ( ModelState.IsValid).

Agora tenho algumas perguntas:

  1. Isso pode ser usado com jQuery ajax submit em vez disso? O que estou fazendo é simplesmente remover o formulário e ao clicar no botão enviar um javascript irá coletar os dados e executar o $.ajax.

  2. O lado do servidor ModelState.IsValidfuncionará?

  3. Como posso encaminhar o problema de validação de volta para o cliente e apresentá-lo como se eu estivesse usando o build int validation ( @Html.ValidationSummary(true))?

Exemplo de chamada 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');
        }
    });
}

Editar 1:

Incluído na 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>
Hera
fonte
Boa resposta abaixo. Aqui está uma questão relacionada. A resposta permite a validação do lado do cliente ou do lado do servidor. Estou apaixonado pelo código JQuery que eles fornecem. (Não, não foi minha resposta.) Stackoverflow.com/questions/28987752/…
Adventure

Respostas:

155

Cliente

Usando o jQuery.validate biblioteca deve ser bem simples de configurar.

Especifique as seguintes configurações em seu Web.configarquivo:

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

Ao construir sua visualização, você definiria coisas assim:

@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: Eles precisam ser definidos em um elemento de formulário

Em seguida, você precisaria incluir as seguintes 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>

Isso deve ser capaz de configurá-lo para validação do lado do cliente

Recursos

Lado do Servidor

NOTA: Isso é apenas para validação adicional do lado do servidor na parte superior da jQuery.validationbiblioteca

Talvez algo assim possa ajudar:

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

Onde ValidateAjaxé um 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;
        }
    }
}

O que isso faz é retornar um objeto JSON especificando todos os erros do seu modelo.

A resposta de exemplo seria

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

Isso seria devolvido ao seu retorno de chamada de tratamento de erros da $.ajaxchamada

Você pode percorrer os dados retornados para definir as mensagens de erro conforme necessário com base nas Chaves retornadas (eu acho que algo como $('input[name="' + err.key + '"]')encontraria seu elemento de entrada

Andrew Burgess
fonte
1
Ótima resposta, especialmente com o incrível ValidateAjaxAttribute! Obrigado!
René
3
Não entendo por que essa resposta obteve tantos votos. Ele não responde à pergunta 1: como fazer a validação do cliente ao postar com $ .ajax? Acho que a resposta @Shyju ajuda com isso.
Valentin
2
@Valentin - minha resposta ajuda porque os dados são validados no lado do servidor também. Os plug-ins de validação devem permitir a validação dinâmica à medida que o formulário é preenchido e, quando o formulário é enviado (no entanto, o OP deseja fazer isso), o servidor fornecerá a validação final, que é preferível de qualquer maneira, pois a validação do lado do cliente pode ser ignorada.
Andrew Burgess
8
Eu utilizo as extensões da mensagem de validação do jQuery fazendo um loop pelos erros retornados e inserindo a mensagem de erro na extensão correta:for (var i = 0; i < modelStateErrors.length; i++) { $('span[data-valmsg-for="' + modelStateErrors[i].key + '"]').text(modelStateErrors[i].errors[0]); }
Ian,
7
Essa resposta é ótima! Mas ainda acho que a estrutura ASP.NET MVC deve fornecer uma maneira embutida de fazer isso.
Zignd
40

O que você deve fazer é serializar os dados do formulário e enviá-los para a ação do controlador. A ASP.NET MVC vinculará os dados do formulário ao EditPostViewModelobjeto (seu parâmetro de método de ação), usando o recurso de vinculação do modelo MVC.

Você pode validar seu formulário no lado do cliente e se estiver tudo bem, enviar os dados para o servidor. O valid()método será ú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
fonte
1
Obrigado! Tentei este $ ("form #" + formId) .validate () mas diz que o formulário (que foi encontrado) não tem um validate ()?
Ivy
Veja Edit1, onde mostro que o script de validação está incluído na página da web. Também vejo que a validação está em execução ao usar um botão de envio de tipo de entrada regular.
Ivy
Encontrei o problema (removi Scripts.Render da masterpage). Mas ainda tem problemas para enviar de volta erros de validação de ModelState para o cliente? Como posso resolver isso? Por exemplo, se o usuário não estiver mais conectado (ModelState.AddModelError ("CustomError", "texto de validação").
Ivy
1
você precisa incluir os arquivos jquery.validate e jquery.validate.unobtrusive js em sua página. As suas entradas HTML têm o atributo que os plug-ins de validação procuram?
Shyju
1
Ivy: Seu tipo de retorno (ActionResult) é uma classe base de JsonResult. para que ele possa retornar dados JSON. O que você deve fazer é, se for uma chamada ajax (verifique Request.IsAjax), obter os erros de validação e construir um JSON e enviá-lo de volta ao cliente. Verifique o json no método de retorno de chamada de $ .post e mostre as mensagens de erro.
Shyju
9

Aqui está uma solução bastante simples:

No controlador, retornamos nossos erros como este:

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

Aqui estão alguns dos scripts do 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>');
    });
}

É assim que lidamos com isso via 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);
        }
    }
});

Além disso, renderizo visualizações parciais por meio de ajax da seguinte maneira:

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
fonte
3
Seu "GetModelStateErrors" pode ser completamente simplificado convertendo-o em uma instrução de linha única mais simples: return ModelState.Values.SelectMany (x => x.Errors) .Select (x => x.ErrorMessage) .ToList ();
Camilo Terevinto
1
Por que não simplesmente retornar a PartialViewpara renderizar para Ajax?
Sinjai
4

Adicionada mais lógica à solução fornecida por @Andrew Burgess. Aqui está a solução completa:

Criado um filtro de ação para obter erros para a solicitação 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;
            }
        }
    }

Adicionei o filtro ao meu 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);
}

Adicionado um script comum para validação 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);
}

Por fim, adicionei o método de erro javascript ao meu formulário Ajax Begin:

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

Você pode fazer desta forma:

( Editar: Considerando que você está esperando uma resposta jsoncom dataType: 'json')

.INTERNET

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

Se precisar também posso explicar como fazer retornando um erro 500, e obter o erro no erro do evento (ajax). Mas, no seu caso, pode ser uma opção

andres descalzo
fonte
1
Eu sei como fazer uma solicitação regular do Jason, entretanto, isso não vai me ajudar com a validação das propriedades diferentes ou mesmo usar a validação embutida do ASP.NET MVC que eu pedi. Eu provavelmente poderia construir um objeto Jason complexo para explicar os erros de validação para cada propriedade, mas isso vai dar muito trabalho e eu espero que você possa reutilizar a funcionalidade interna da validação do ASP.NET MVC para isso.
Ivy
1 - Como executar sua função "SendPost"? E 2 - seus dados válidos no cliente?
andres descalzo
Por favor, explique? Não recebeu sua última postagem?
Ivy