266

Is there a tutorial or code example of using Ajax.BeginForm within Asp.net MVC 3 where unobtrusive validation and Ajax exist?

This is an elusive topic for MVC 3, and I cannot seem to get my form to work properly. It will do an Ajax submit but ignores the validation errors.

Dave Markle
  • 88,065
  • 20
  • 140
  • 165
JBeckton
  • 6,961
  • 13
  • 48
  • 70

8 Answers8

428

Example:

Model:

public class MyViewModel
{
    [Required]
    public string Foo { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel());
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return Content("Thanks", "text/html");
    }
}

View:

@model AppName.Models.MyViewModel

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
<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>

<div id="result"></div>

@using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "result" }))
{
    @Html.EditorFor(x => x.Foo)
    @Html.ValidationMessageFor(x => x.Foo)
    <input type="submit" value="OK" />
}

and here's a better (in my perspective) example:

View:

@model AppName.Models.MyViewModel

<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>
<script src="@Url.Content("~/Scripts/index.js")" type="text/javascript"></script>

<div id="result"></div>

@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Foo)
    @Html.ValidationMessageFor(x => x.Foo)
    <input type="submit" value="OK" />
}

index.js:

$(function () {
    $('form').submit(function () {
        if ($(this).valid()) {
            $.ajax({
                url: this.action,
                type: this.method,
                data: $(this).serialize(),
                success: function (result) {
                    $('#result').html(result);
                }
            });
        }
        return false;
    });
});

which can be further enhanced with the jQuery form plugin.

pb2q
  • 54,061
  • 17
  • 135
  • 139
Darin Dimitrov
  • 960,118
  • 257
  • 3,196
  • 2,876
  • 41
    I agree about using jQUery for Ajax. I think that vast majority of Asp.net MVC Ajax applications rather use jQuery than the built-in Ajax extensions. – Robert Koritnik Mar 24 '11 at 17:47
  • 6
    I am using something like the following and the result seems to be going to its own page and not just replacing a div result. Do you know why? – David Apr 11 '11 at 00:33
  • 3
    Yes, I agree too in using pure jQuery for ajax, using the MVC ajax extension means you need to unnecessary learn other rules and syntax to, in the end, use jQuery. So even I need to write more, but is better do it the right way, plus you get more control and flexibility. – Nestor May 23 '11 at 01:06
  • 3
    @darin-dimitrov: when I try your latter example, I must add data: $('form').serialize(),to the ajax() call. Otherwise, no form data are passed and my model is invalid on the server side. Wonder if there is something I've overlooked? – Brett Oct 31 '11 at 16:14
  • @Brett, there is nothing you have overlooked. It's me that simply forgot this in my answer. I have updated it now. Thanks for pointing this out. – Darin Dimitrov Oct 31 '11 at 16:57
  • 2
    @DarinDimitrov what if there is an error with the BLL and you need to send the model back to the View and show the error message because hardened layer provided deeper validation on the data and found an issue. Just relying on the Client side validation isn't enough. You can't return View(model); now because the whole view gets rendered in the result div... what is the workaround for that? – CD Smith Mar 23 '12 at 15:15
  • 1
    @CDSmith, I don't understand your question. When you return `View(model);` you are passing along all modelstate errors associated to properties. And if you have some other business validation errors you could add them with `ModelState.AddModelError` inside the controller. Then when you redisplay the view with `return View(model);` you will use the `Html.ValidationSummary` helper to show them – Darin Dimitrov Mar 23 '12 at 15:17
  • @DarinDimitrov exactly, that's what I assumed as well. Try this: take your above example and modify your action to be [HttpPost] public ActionResult Index(MyViewModel model) { // force an error as if we had an error in a BLL ModelState.AddModelError("","Error"); if(ModelState.IsValid) return Content("Thanks " + model.Foo, "text/html"); return View(model); } this simulates an error and returns the View(model). It renders the whole View inside the result div and shows a multiple overlayed duplicate page – CD Smith Mar 23 '12 at 15:32
  • @CDSmith, you should be returning a partial from your controller action or you should disable the layout. – Darin Dimitrov Mar 23 '12 at 15:35
  • @DarinDimitrov I figured that I could have a standard Success message to return and evaluate it on success of the Ajax call: if( result.startsWith("Success") { show the result in the div. But if Success isn't there then don't do anything. Except that doesn't work. The view doesn't evaluate the validation errors and nothing is shown in summary. it gets swallowed – CD Smith Mar 23 '12 at 15:38
  • @DarinDimitrov PartialView(model) also fails. I tried that as well. It doesn't duplicate the entire view in the div, just the model portion so for the above example the result shows 2 textboxes, labels and buttons – CD Smith Mar 23 '12 at 15:39
  • @DarinDimitrov Hi, how can you can associate jQuery submit action if the Html.Begin is the result of a list a links? like option1option2 – Patrick Mar 06 '13 at 17:07
  • Why do you like the Html.BeginForm approach over Ajax.BeginForm since the former requires to remember adding the extra scription for it to work? Thx. – Dave Sep 17 '13 at 22:13
  • @Dave, Ajax.BeginForm also requires an extra script to work: the jquery.unobtrusive-ajax.js script with the exception that this script provides you with less control over the request being made compared to the custom script solution. – Darin Dimitrov Sep 18 '13 at 06:15
  • 1
    @David: Yes, this will be load the entire page within the `div` you specified. In order, not to do this, you have to go to your `action-method` and determine if the request is of `Ajax` type using `Request.IsAjaxRequest` and if yes, you return a `partial-view` passing in the model. Else, you just return a `View` containing the model. – now he who must not be named. Oct 08 '13 at 13:53
  • I am trying this and it works perfectly (the Html.BeginForm method) with one exception. When I submit the form in the dialog with an intentional error, I get 2 copies of the form within the dialog, one with the validation errors and another without. Any ideas? – steveareeno Aug 29 '14 at 15:08
  • I think the closing tag of the result div should be written at the end, after the `@using (Html.BeginForm())` scope, that would probably solve @steveareeno problem as well. – BornToCode Dec 30 '14 at 11:09
54

I think that all the answers missed a crucial point:

If you use the Ajax form so that it needs to update itself (and NOT another div outside of the form) then you need to put the containing div OUTSIDE of the form. For example:

 <div id="target">
 @using (Ajax.BeginForm("MyAction", "MyController",
            new AjaxOptions
            {
                HttpMethod = "POST",
                InsertionMode = InsertionMode.Replace,
                UpdateTargetId = "target"
            }))
 {
      <!-- whatever -->
 }
 </div>

Otherwise you will end like @David where the result is displayed in a new page.

Dror
  • 1,336
  • 11
  • 15
  • 7
    David's issue is almost always caused from not including the jqueryval bundle which contains the unobtrusive ajax code. Be very careful with this approach you posted otherwise you'll get one post and then your form is hosed since you've just replaced it. You then require your "MyAction"s view to manage its form and re-specify all the ajax options in it. – Adam Tuliper - MSFT Sep 18 '13 at 07:40
  • In my application targeted div showing whole form with master page please help me – Nitin... Jul 26 '16 at 10:34
  • For me I had not set `UnobtrusiveJavaScriptEnabled` to true anywhere – Kunal Dec 06 '18 at 04:20
15

I got Darin's solution working eventually but made a few mistakes first which resulted in a problem similar to David (in the comments below Darin's solution) where the result was posting to a new page.

Because I had to do something with the form after the method returned, I stored it for later use:

var form = $(this);

However, this variable did not have the "action" or "method" properties which are used in the ajax call.

$(document).on("submit", "form", function (event) {
    var form = $(this);

    if (form.valid()) {
        $.ajax({
            url: form.action, // Not available to 'form' variable
            type: form.method,  // Not available to 'form' variable
            data: form.serialize(),
            success: function (html) {
                // Do something with the returned html.
            }
        });
    }

    event.preventDefault();
});

Instead you need to use the "this" variable:

$.ajax({
    url: this.action, 
    type: this.method,
    data: $(this).serialize(),
    success: function (html) {
        // Do something with the returned html.
    }
});
Jason
  • 7,950
  • 9
  • 53
  • 65
  • 5
    That's because the form variable you have set it the `jQuery` object with form as the selector. `form[0]` would have the properties. It's also good practise to prefix any `jQuery` variables with `$` to more easily identify them. – James South May 29 '12 at 20:35
6

Darin Dimitrov's solution worked for me with one exception. When I submitted the partial view with (intentional) validation errors, I ended up with duplicate forms being returned in the dialog:

enter image description here

To fix this I had to wrap the Html.BeginForm in a div:

<div id="myForm">
    @using (Html.BeginForm("CreateDialog", "SupportClass1", FormMethod.Post, new { @class = "form-horizontal" }))
    {
        //form contents
    }
</div>

When the form was submitted, I cleared the div in the success function and output the validated form:

    $('form').submit(function () {
        if ($(this).valid()) {
            $.ajax({
                url: this.action,
                type: this.method,
                data: $(this).serialize(),
                success: function (result) {
                    $('#myForm').html('');
                    $('#result').html(result);
                }
            });
        }
        return false;
    });
});
steveareeno
  • 1,795
  • 5
  • 37
  • 51
  • I too get the same error. I am using `Partial Views` to render the create function below the index page. I can get all the validation messages in the partial View. But when the `Create` is successful the index is displayed twice. I have no `Html.BeginForm` in my Index View. – Vini Nov 02 '15 at 08:35
  • Try using `UpdateTargetId = "myForm"` instead – Kunal Dec 06 '18 at 04:30
4

If no data validation excuted, or the content is always returned in a new window, make sure these 3 lines are at the top of the view:

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
cheny
  • 1,999
  • 18
  • 24
3

Example

//In Model

public class MyModel
{  
   [Required]
    public string Name{ get; set; }
}

//In PartailView //PartailView.cshtml

@model MyModel

<div>
    <div>
      @Html.LabelFor(model=>model.Name)
    </div>
    <div>
        @Html.EditorFor(model=>model.Name)
        @Html.ValidationMessageFor(model => model.Name)
    </div>
</div>

In Index.cshtml view

@model MyModel
<div id="targetId">
    @{Html.RenderPartial("PartialView",Model)}
</div>

@using(Ajax.BeginForm("AddName", new AjaxOptions { UpdateTargetId = "targetId", HttpMethod = "Post" }))
{
     <div>
        <input type="submit" value="Add Unit" />
    </div>
}

In Controller

public ActionResult Index()
{
  return View(new MyModel());
}


public string AddName(MyModel model)
{
   string HtmlString = RenderPartialViewToString("PartailView",model);
   return HtmlString;
}


protected string RenderPartialViewToString(string viewName, object model)
        {
            if (string.IsNullOrEmpty(viewName))
                viewName = ControllerContext.RouteData.GetRequiredString("action");

            ViewData.Model = model;

            using (StringWriter sw = new StringWriter())
            {
                ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
                ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
                viewResult.View.Render(viewContext, sw);
                return sw.GetStringBuilder().ToString();
            }
        }

you must be pass ViewName and Model to RenderPartialViewToString method. it will return you view with validation which are you applied in model and append the content in "targetId" div in Index.cshtml. I this way by catching RenderHtml of partial view you can apply validation.

Shivkumar
  • 1,893
  • 5
  • 19
  • 32
3

Ajax forms work asynchronously using Javascript. So it is required, to load the script files for execution. Even though it's a small performance compromise, the execution happens without postback.

We need to understand the difference between the behaviours of both Html and Ajax forms.

Ajax:

  1. Won't redirect the form, even you do a RedirectAction().

  2. Will perform save, update and any modification operations asynchronously.

Html:

  1. Will redirect the form.

  2. Will perform operations both Synchronously and Asynchronously (With some extra code and care).

Demonstrated the differences with a POC in below link. Link

Gone Coding
  • 88,305
  • 23
  • 172
  • 188
1

Prior to adding the Ajax.BeginForm. Add below scripts to your project in the order mentioned,

  1. jquery-1.7.1.min.js
  2. jquery.unobtrusive-ajax.min.js

Only these two are enough for performing Ajax operation.