Using Ajax.BeginForm with ASP.NET MVC 3 Razor
Asked Answered
L

8

267

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.

Languedoc answered 23/3, 2011 at 18:44 Comment(0)
W
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.

Whitton answered 23/3, 2011 at 18:51 Comment(17)
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.Plagal
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?Overreact
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.Satellite
@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?Aquilegia
@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.Whitton
@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?Papal
@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 themWhitton
@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 pagePapal
@CDSmith, you should be returning a partial from your controller action or you should disable the layout.Whitton
@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 swallowedPapal
@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 buttonsPapal
@DarinDimitrov Hi, how can you can associate jQuery submit action if the Html.Begin is the result of a list a links? like <a>option1</a><a>option2</a>Edwinedwina
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.Doehne
@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.Whitton
@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.Harebrained
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?Goldston
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 @Goldston problem as well.Festination
E
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.

Eliathan answered 20/12, 2012 at 15:35 Comment(3)
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.Dent
In my application targeted div showing whole form with master page please help mePhrasing
For me I had not set UnobtrusiveJavaScriptEnabled to true anywhereAmelina
S
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.
    }
});
Scrotum answered 14/3, 2012 at 17:50 Comment(1)
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.Fisch
G
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;
    });
});
Goldston answered 29/8, 2014 at 16:43 Comment(2)
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.Vachell
Try using UpdateTargetId = "myForm" insteadAmelina
Q
5

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>
Quincy answered 1/12, 2013 at 8:9 Comment(1)
I didn't find them in solution. I had to install them from Nuget package managerSpahi
L
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

Links answered 14/1, 2012 at 13:31 Comment(0)
B
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.

Botheration answered 13/9, 2012 at 7:47 Comment(0)
W
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.

Wellrounded answered 27/6, 2017 at 8:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.