Display Error message in Validationsummary with Ajax

What'sUP picture What'sUP · Oct 21, 2017 · Viewed 8k times · Source

Goal:
If you retrieve a error based on the input, it should be displayed in ValidationSummary in relation to ajax without the webpage being refreshed.

Problem:
I have tried to make it but it doesn't work so well.

What part am I missing?

Thank you!

Info:
*I have found some webpages but they do not fit exactly to my purpose.
*I'm using ASP.net mvc

@model WebApplication1.Controllers.PersonnelModel

@{
    ViewBag.Title = "Ajax";
}

<h2>Ajax</h2>



<h2>AjaxViewModel</h2>
@using (Html.BeginForm("HtmlViewModel", "Home", null))
{
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>PersonnelModel</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.UserName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UserName)
            @Html.ValidationMessageFor(model => model.UserName)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.MailAdress)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.MailAdress)
            @Html.ValidationMessageFor(model => model.MailAdress)
        </div>
    </fieldset>
    <p>
        <input type="submit" value="Html Form Action" />
    </p>
}

<br/>
<br />




<h2>AjaxViewModel</h2>
@using (Ajax.BeginForm("AjaxViewModel", "Home", new AjaxOptions { UpdateTargetId = "result" }))
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>PersonnelModel</legend>

        <div id="result"></div>
        <div class="editor-label">
            @Html.LabelFor(model => model.UserName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UserName)
            @Html.ValidationMessageFor(model => model.UserName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.MailAdress)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.MailAdress)
            @Html.ValidationMessageFor(model => model.MailAdress)
        </div>
    </fieldset>
    <p>
        <input type="submit" value="Ajax Form Action" />
    </p>
}



<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/jquery.unobtrusive-ajax.min.js"></script>

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace WebApplication1.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }


        [HttpPost]
        public ActionResult HtmlViewModel(PersonnelModel Pmodel)
        {
            return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
        }


        [HttpPost]
        public ActionResult AjaxViewModel(PersonnelModel Pmodel)
        {
            /*
            ModelState.AddModelError("", "login is fail");
            return View("Index", Pmodel);
            */

            return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
        }

    }



    public class PersonnelModel
    {
        [Required(ErrorMessage = "UserName Required.")]
        public string UserName { get; set; }

        [Required(ErrorMessage = "Email id Required.")]
        public string MailAdress { get; set; }
    }

}

Answer

Shyju picture Shyju · Oct 21, 2017

EDIT - 03/11/2017 : There is an easy way to do this

Create a partial view for the form, Let's call it Form.cshtml and move the markup needed for the form to that. For your ajax form, set the data-ajax-mode attribute value to replace and data-ajax-update value to the id of the same form.

If you are using Ajax.BeginForm helper method, this is how you will do

@model PersonnelModel
@using (Ajax.BeginForm("Create", "Home", 
     new AjaxOptions { UpdateTargetId = "myform", 
                              InsertionMode = InsertionMode.Replace },new { id="myform"}))
{
    @Html.ValidationSummary("", true)

    @Html.TextBoxFor(f => f.UserName)
    @Html.ValidationMessageFor(model => model.UserName)

    @Html.TextBoxFor(f => f.MailAdress)
    @Html.ValidationMessageFor(model => model.MailAdress)

    <input type="Submit" id="submit" value="Submit" class="btn btn-default" />    
}

Now in your main view, simple call this partial view

@model PersonnelModel
<h2>My form below</h2>
@Html.Partial("Form",Model)

Now in the action method, when model state validation fails, return the partial view.

public ActionResult Create(PersonnelModel model)
{
   if (ModelState.IsValid)
   {
      // to do : Save
   }
   if (Request.IsAjaxRequest())
   {
      return PartialView("Form",model);
   }
   return View(model);
}

Now when you submit the form and model state validation fails, the action method code will return the partial view result with the validation error messages (generated by the validation helpers) and the jquery.unobtrusive-ajax.js library code will replace (because we specified that with data-ajax-mode="replace") the content of the result of the jquery selector #data-ajax-update (the form tag and it's inner contents) with the response coming back from the server.

This should do it. Less client side code, compared to my old approach (below)


The Html.ValidationSummary method will be executed when the razor view gets executed. If you are doing a normal form post (non ajax), your action method usually returns to the same view when Model validation fails (assuming you write code like that) and the razor view code gets executed and the ValidationSummary method will read the validation errors from the model state dictionary and render the error messages.

When you use Ajax.BeginForm helper method the helper will generate some extra data attributes on the form and as long as you have included the jquery.unobtrusive-ajax.min.js script file, the form submit will be hijacked and it will do an ajax form submit instead.

When you do the ajax form submit, if you want to render model validation messages, you need to explicitly read the model validation errors and return that as a JSON response which your client side code can read and display in the UI.

[HttpPost]
public ActionResult Index(PersonnelModel  model)
{
  if (ModelState.IsValid)
  {
      return Json(new {status = "success", message= "Thanks for the details"});
  }
  else
  {
     var errors = new List<string>();
     foreach (var modelStateVal in ViewData.ModelState.Values)
     {
        errors.AddRange(modelStateVal.Errors.Select(error => error.ErrorMessage));
     }
     return Json(new {status = "validationerror", errors = errors});
  }
}

Now in your view, make sure you have a success handler for your ajax begin form to handle the response json

@using (Ajax.BeginForm("Index", "Home", new AjaxOptions { OnSuccess = "OnSuccess", }))
{
    @Html.ValidationSummary("", true)
    @Html.TextBoxFor(model => model.MailAdress)

    <!--Your other form input elements-->
     <input type="submit" value="Html Form Action" />
}

Note that i used @Html.ValidationSummary method with 2 overloads and passing an empty string as the first param. This will always render a ul element within the div which has class validation-summary-valid.

Now create your OnSuccess function and check the response and see whether response has a status property and the value of it is validationerror. If yes, loop through the errors collection and add a new li element with the error.

function OnSuccess(response) {
    var $summaryUl = $(".validation-summary-valid").find("ul");
    $summaryUl.empty();

    if (response.status === 'validationerror') {
        $.each(response.errors,
            function(a, b) {
                $summaryUl.append($("<li>").text(b));
            });
    } else {
        alert(response.message);
    }
}