Using Unobtrusive Ajax Forms in ASP.Net MVC3

This post covers how to use the new unobtrusive libraries in ASP.net MVC3 to provide an improved form submission user experience utilising AJAX. The idea is to have the whole form contents submitted and then replaced by the server generated content, be it either the submitted form with validation errors, or a success message.

First, you need to ensure that you reference the required javascript – jquery.unobtrusive-ajax.js (or min.js for production).


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

Each Ajax form requires at least two views. The first view is a wrapper around the actual form content, while second view is a partial view that contains the form itself. To demonstrate what I mean, I have created a simple registration form (note how it used the new IValidatableObject interface for complex model validation at binding):


namespace Registration.Models
{
  public class RegistrationFormModel: IValidatableObject
  {
    [Required]
    public string Firstname
    {
      get;
      set;
    }

    [Required]
    public string Surname
    {
      get;
      set;
    }

    [Required]
    public int Age
    {
      get;
      set;
    }
    
    public string RegsitrationUniqueId
    {
      get;
      set;
    }

    #region IValidatableObject Members

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext){
      if (Age < 21){
        yield return new ValidationResult("Age must be at least 21 years.", new string []{ "Age" });
      }
    }

    #endregion
  }
}

The outer form is just a container that holds the dynamic content, be it the data entry form or the success message. I've included the current date time to demonstrate how the outer wrapper does not change when the inner form content is submitted.



@{
    ViewBag.Title = "MyForm";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Register Online - @DateTime.Now.ToString()</h2>

<div id="formContent">
    @{Html.RenderPartial("FormContent");}
</div>


As you can see, it merely renders the content of the partial view inside an element that becomes the target of our ajax post result. The form content, for the purpose of this blog, is trivial. Note how I have used the Post option in the AjaxOptions - this is because I want data submitted to the Index() overload of the controller that accepts HttpPost.


@model Registration.Models.RegistrationFormModel
           
@{
    AjaxOptions options = new AjaxOptions{
        HttpMethod = "Post",
        UpdateTargetId = "formContent"        
    };        
    
}

@using (Ajax.BeginForm(options)) {    
  <fieldset>
    <legend>Registration Form</legend>
    @Html.ValidationSummary(true)

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

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


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

    <div>
      <input type="submit" value="Register" />
    </div>
  </fieldset>
}

I have added an additional partial view which will be rendered when the form is successfully submitted:


@model Registration.Models.RegistrationFormModel
           
<h3>You have successfully registered.</h3>

<p>Your regsitration number is @Model.RegsitrationUniqueId</p>

The controller for this form for the purposes of this demo is pretty simple. If the model is valid then a registration is calculated but whatever means and the Success view is returned. If the model is invalid (becuiase the IValidatableObject interface has returned a model error, then the data-entry form is returned back to the user to fix their errors.

public class FormController : Controller{

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

  [HttpPost]
  public PartialViewResult Index(RegistrationForm model){
    if (ModelState.IsValid){               
      //go and do registration business logic,
      RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();

      byte [] regsitrationBytes = new byte[16];
      csp.GetBytes(regsitrationBytes);
      model.RegsitrationUniqueId = Convert.ToBase64String(regsitrationBytes);
      return PartialView("Success", model);
    }
    else
    {
      //return the data entry form
      return PartialView("FormContent", model);
    }            
 }
}

The project layout is as follows:

When the user navigates to the registration page:

When the user attempt to register a user who is too young, server side validation kicks in and the data entry form is returned:

When the data is correctly entered, the success message is displayed:

Note how in all the images the date/time that the registration startd has not changed - there has been no full post back to the server.

Of interest is how the unobtrusive ajax is rendered to the client - without masses of javascript but rather HTML5 valid element attributes on the form element as follows:

<form action="/PVA/Form/" data-ajax="true" data-ajax-method="Post" data-ajax-mode="replace" data-ajax-update="#formContent" id="form0" method="post">
...
</form>

Conclusion

This post has demonstrated how easy it is to provide a superior user experience using Ajax forms and the unobstrusive javascript libraries in ASP.Net MVC 3.

kick it on DotNetKicks.com

About these ads
This entry was posted in Ajax, ASP.Net MVC, Javascript and tagged , . Bookmark the permalink.

12 Responses to Using Unobtrusive Ajax Forms in ASP.Net MVC3

  1. Alok says:

    Hi XHalent,

    I tried to create an Ajax application with the details provided above, but not sure where I am missing. Whenever I enter age less than 21 I am moved to FormContent.aspx (I am using aspx pages), instead of remaining on the same page with error. Could you please help me out. I am using VS 2010 with MVC3.

    If you have a link where I could download the sample application then please let me know.

    Thanks
    Alok

  2. Charles Huaba says:

    Thanks, that helped a lot!

    But i still struggle with one problem: What if youve got a model for the “master view” and a separate viewmodel for the partial view?

    You are using
    public ActionResult Index(){
    return View(new RegistrationFormModel()); (i assume you forgot the “Model”)
    }

    How would you pass one model to the master page and the empty one to the partialview?

  3. zahidadeel says:

    what if we have two divs formcontent and result and i want registration number to be placed in result div if there are no model errors and form with validation errors to be placed in formcontent div. How would we solve this problem. i have been facing this problem for a while now. i have a comment form rendered below a list of comments and i want successful comments to be added in list of comments and if there is model error it should be appearing with the form which is of course in another element

    • xhalent says:

      If I understand you correctly, you want a div, separate to your form, to update when the form is submitted. I think it’s pretty straightfoward. Add a script function to your page that will re-load the list of rendered comments via ajax using the jQuuery .load method. Then specify the OnComplete function in the AjaxOptions for the form as the function name you’ve just created. Very similar to how I’ve made the list of entries refresh itself when a detail record is updated in this post about master-detail using jquery dialogs.

  4. Brennan says:

    Hey, I tried following your example but I cannot get it to work. Instead of returning the partialview it returns an entire page with the master page. It’s super annoying. Any tips?

    • xhalent says:

      Are you sure you are returning PartialViews from your Controllers, and not Views? ie return PartialView(..) not return View(…)

  5. vascopp says:

    Hey, thanks a lot for this post! I have a question though, if you have a server side validation (like a check for username uniqueness on a register form) when the ajax onsuccess finishes, I get my error on the form as expected. However, all of the subsequent form interactions stop having jquery client side validation active on them after this and only validate after another ajax request (server side)… Do you have any ideas on what might be the problem?

    • xhalent says:

      Thanks, your problem sounds like you need to re-run the unobtrusive validators manually as they only load automagically on a full page load.

      • vascopp says:

        Yes that is totally right, I had manage to deduct that but forgot to update. Thank you very much for your time :)

  6. dfdfd says:

    how hard is it to include the code? some people can look at that at not have to waste there time reading your crap

    • xhalent says:

      I dunno, how hard is it not to be jerk? I’m on WordPress so if they do file share… Feel free to tell me how it works otherwise I’m too busy earning a crust….

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s