Asynchronous Controllers Calls to Save Data in ASP.NET MVC 3

On a few occasions I wondered if it is possible to simulate Silverlight Save behavior in ASP.NET MVC.  What I mean is to asynchronously call a service from an edit form, asynchronously process the results, and update view based on saved data.  This process is a bit more complicated in ASP.NET MVC because we have to process potential errors and update UI to include errors.  Here is the workflow I am trying to implement:

Create new entry screen –> Hit Save –> Submit data to the controller –> Save data –> Return updated view –> Update UI based on updated View, including populating newly generated ID for subsequent saves.

Here is my solution outline

  • Create partial view for the form to enter data for an entity, Attendee in my case.
  • Create Controller method to accept Attendee data from UI to save
  • Return new partial view from this method
  • Push new view into DOM.

I am now going to break down my solution.  I am using the following Attendee class as the entity for entry screen.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ComponentModel.DataAnnotations;

using MvcEFCodeFirst.Resources;

 

namespace MvcEFCodeFirst.Data

{

  public class Attendee

  {

    [Key]

    public int AttendeeID { get; set; }

 

    [StringLength(50,

      ErrorMessageResourceName = "FirstName50Chars",

      ErrorMessageResourceType = typeof(Resource))]

    [Required(

      ErrorMessageResourceName = "FirstNameRequired",

      ErrorMessageResourceType = typeof(Resource))]

    [Display(Name = "FirstName", ResourceType = typeof(Resource))]

    public string FirstName { get; set; }

 

    [StringLength(50,

      ErrorMessageResourceName = "LastName50Chars",

      ErrorMessageResourceType = typeof(Resource))]

    [Required(

      ErrorMessageResourceName = "LastNameRequired",

      ErrorMessageResourceType = typeof(Resource))]

    [Display(Name = "LastName", ResourceType = typeof(Resource))]

    public string LastName { get; set; }

 

    [StringLength(250)]

    [Required(

     ErrorMessageResourceName = "EmailRequired",

     ErrorMessageResourceType = typeof(Resource))]

    [RegularExpression(

      @"^[A-Za-z0-9](([_.-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([.-]?[a-zA-Z0-9]+)*).([A-Za-z]{2,})$",

      ErrorMessageResourceName = "EmaiInvalidFormat",

      ErrorMessageResourceType = typeof(Resource))]

    [Display(Name = "Email", ResourceType = typeof(Resource))]

    public string Email { get; set; }

 

    [StringLength(int.MaxValue)]

    [Display(Name = "Notes", ResourceType = typeof(Resource))]

    public string Notes { get; set; }

 

    public virtual ICollection<Session> Sessions { get; set; }

  }

}

 

Now I am going to use scaffolding to create new controller that is linked to Attendee class:  I am right-clicking on Controllers folder and selecting new Controller item

 

image

The create, display, etc… views have been created.  Now, I am going to extract form screen for Attendee edit into a partial view.  Here is what this wizard screen looks like.

image

 

The view is pretty simple, and here is what it looks like:

@model MvcEFCodeFirst.Data.Attendee

@using (Html.BeginForm(new { Id = "AttendeeForm" }))

{

  @Html.ValidationSummary(true)

  <fieldset>

    <legend>Attendee</legend>

    <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.LastName)

    </div>

    <div class="editor-field">

      @Html.EditorFor(model => model.LastName)

      @Html.ValidationMessageFor(model => model.LastName)

    </div>

    <div class="editor-label">

      @Html.LabelFor(model => model.Email)

    </div>

    <div class="editor-field">

      @Html.EditorFor(model => model.Email)

      @Html.ValidationMessageFor(model => model.Email)

    </div>

    <div class="editor-label">

      @Html.LabelFor(model => model.Notes)

    </div>

    <div class="editor-field">

      @Html.EditorFor(model => model.Notes)

      @Html.ValidationMessageFor(model => model.Notes)

    </div>

    @if (Model.AttendeeID > 0)

    {

      @Html.HiddenFor(model => model.AttendeeID);

    }

  </fieldset>

}

 

Now, I am going to integrate this new partial view into Create/Edit shell views for Attendee.  I am going to consolidate Create and Edit views into single view.  This is more complicated now, and I am going to document the code below in more details

@if (false)

{   <script src="../../Scripts/jquery-1.5.1-vsdoc.js" type="text/javascript"></script>}

@model MvcEFCodeFirst.Data.Attendee

@{

  Layout = "~/Views/Shared/_Layout.cshtml";

}

<h2>

  @ViewBag.Title</h2>

<div id="formDiv">

  @{Html.RenderPartial("AttendeePartial", Model);}

</div>

<div>

  <button id="saveNewAttendee">

    @ViewBag.ButtonText

  </button>

</div>

<div>

  @Html.ActionLink("Back to List", "Index")

</div>

<script type="text/javascript">

  $().ready(function () {

    $(‘#saveNewAttendee’).click(function () {

      $.blockUI({ message: ‘<h1>Updating…</h1>’ });

      $.ajax({

        type: "POST",

        url: "./CreateUpdateAjax",

        data: $(‘form’).first().serialize(),

        success: function (data, status, xhr) {

          $(‘#formDiv’).html(data);

          if ($(‘#AttendeeID’).val() != undefined) {

            $(‘#saveNewAttendee’).html(‘Save’);

          };

          $.unblockUI();

        },

        error: function (xhr, status, error) {

          $.unblockUI();

          alert("error");

        }

      });

    })

 

  })

 

</script>

 

 

The top of the file is imply there to get IntelliSense for jQuery.  The next interesting code is RenderParital call.  This is only used during initial call to the view.  The form that is used for data entry is within partial view.  The last portion is javascript that is used to post the form.  First of all I need to block UI while the save is proceeding.  I downloaded and am using blockUI jQuery extension. You can download it here.  Next, I am using ajax method to submit the form data.  I am using convenient serialize method to serialize the form’s fields to be sent to the server.  I am using POST method to submit the data.  I am also specifying error and success functions.  In success function I have to first check to see if the save succeeded to create new Attendee.  I am checking this by inspecting a hidden field for Attendee ID that should have been inserted after successful save.  Then, I unblock UI.

Now, let’s see what the controller method CreateUpdateAjax looks like:

    [HttpPost]

    public ActionResult CreateUpdateAjax(Attendee attendee)

    {

      if (ModelState.IsValid)

      {

        if (attendee.AttendeeID == 0)

        {

          db.Insert(attendee);

        }

        else

        {

          db.SetModified(attendee);

          db.Save();

        }

      }

      return PartialView("AttendeePartial", attendee);

    }

 

This method is very simple.  I am relying on MVC to convert form data magically into an Attendee object.  Then I am checking for errors.  If everything succeeds, I am inserting or updating the attendee using my repository class, then returning a partial view.  I am skipping repository for now, you can read about it in my previous posts.  Just search for repository in top right corner of the blog.

In conclusion as you can see it does not take much more code to simulate Silverlight behavior in ASP.NET MVC.  Of course, if you try to create a one-page MVC application, you will find that you will need to write a lot more code.  Then again, do your users expect a single page web application?  Anyway, my goal in this post was to show how to do asynchronous saves, and I thinks this approach works pretty well.

Thanks.

4 Comments

  1. Pingback: More on Client Validation in ASP.NET MVC 3 « Sergey Barskiy's Blog

  2. Thanks for the blog! I am doing something similar and was thinking of using partial views with jQuery so it is nice to see someone confirming this works before I set out to implement my design.

    @Html.Action(“Blog”, new { reaction = “Awesome” })

Leave a Reply

Your email address will not be published. Required fields are marked *