More on Asynchronous Processing in ASP.NET MVC 3

In this post I would like to cover a couple of asynchronous patterns that are available in ASP.NET MVC and jQuery as it is used in MVC.

In all my previous examples related to MVC 3.0 I used regular controller class that is automatically created for a developer when New Item –> Controller is chosen from right-click menu on Controller folder in MVC application project.  Now, there is another type of controller that is available in MVC that is not often used –  AsyncControllerThis controller allows you to break your controller actions into two parts – Begin and End methods used in a typical asynchronous patter.  Just like a lot of other functionality in MVC, this pattern follows the same naming conventions.  For example, if you would like to break “Index” controller action into two methods – begin and end, you would end up with IndexAsync and IndexCompleted methods.  As a result, if you navigate to Index url, the former method will automatically be invoked, and when it flags action as completed, IndexCompleted method will be invoked.  Here is code the illustrates this point, calling database, and more specifically EF Code First, asynchronously.

  public class AttendeeController : AsyncController

  {

    private CodeStockContext db = new CodeStockContext();

 

    //

    // GET: /Attendee/

 

    public void IndexAsync()

    {

      AsyncManager.OutstandingOperations.Increment();

      BackgroundWorker worker = new BackgroundWorker();

      worker.DoWork += (o, e) =>

      {

        using (var context = new CodeStockContext())

        {

          AsyncManager.Parameters["attendees"] = context.Attendees.OrderBy(_ => _.LastName).ThenBy(_ => _.FirstName).ToList();

        }

      };

      worker.RunWorkerCompleted += (o, e) => { AsyncManager.OutstandingOperations.Decrement(); };

      worker.RunWorkerAsync();

 

    }

 

 

    public ViewResult IndexCompleted(IEnumerable<Attendee> attendees)

    {

      return View(attendees);

    }

 

As you can see, there is also parameter matching going on, where parameters of AsyncManager are automatically matched to parameters to Completed method.  The only other interesting point, is Increment and Decrement methods of AsyncManager that keep track of pending operations.  If you have multiple operations, you call increment with a parameter corresponding to a number of asynchronous operations.

Why use this pattern?  It has something to do with how IIS processes requests.  When a request comes in, IIS grabs a worker thread from the pool and processes incoming request on this thread.  If you have too many requests, IIS may run out of available threads.  So, if you have a long running IO bound operation, you can dispatch it on a background thread, thus releasing worker thread back into the IIS pool until it is needed, increasing throughput of your web site.  You can read more on this subject here.

Now, there is one more use for asynchronous processing in MVC.  I would like to demonstrate how to use ajax methods on jQuery.  You can use ajax method or post/get methods.  In my case, I am going to demonstrate how to use post method to submit a form’s data to a controller’s method without postback.  Why do this?  At the expense of complexity, you can develop a user interface very similar to windows forms interface.  So, I would likely not go this route without specific user requirements. 

So, now back to the code.  Here is what my view would look for Create action for a class called Attendee.  Frist, let me show you Attendee class:

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, in my view I have regular Html form, but without submit input element:

 

@model MvcEFCodeFirst.Data.Attendee

@{

  ViewBag.Title = "Create";

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

}

<h2>

  Create</h2>

@using (Html.BeginForm())

{

  @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>

  </fieldset>

}

<div>

  <button id="saveNewAttendee">

    Create</button>

</div>

<div>

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

</div>

<script type="text/javascript">

  $().ready(function () {

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

      $.post(‘./CreateAjax’, $(‘form’).first().serialize(), function (o) {

        alert(o);

        if (o === ‘OK’)

          window.location.href = ‘./Index’;

      });

 

    })

  })

</script>

 

Now, instead of submit button, you will see a regular button with ID of saveNewAttendee.  In jQuery ready method, I am attaching a click event handler to this button, in which I am simulating the submit method.  I am calling CreateAjax method on my controller, and I am passing data array created by serializing data collected in the form.  All this is available in jQuery.  I am also passing in function that will be fired when method completes.  Here is the code for the controller method:

    [HttpPost]

    public string CreateAjax(Attendee attendee)

    {

      if (ModelState.IsValid)

      {

        db.Attendees.Add(attendee);

        db.SaveChanges();

        return "OK";

      }

 

      return string.Concat(

        ModelState.SelectMany((state) => state.Value.Errors

          .Select(e => e.ErrorMessage + Environment.NewLine)).ToArray());

    }

 

The code is pretty simple.  I am checking to make sure the model is valid, and if it is, I am saving new attendee.  Otherwise, I am create a string containing all error messages using Linq.  ModelState class contains all the validation data you could possibly need to parse the errors related to validation of your model.  It uses attributes I placed on my Attendee class to do so.  As I mentioned before, attributes are used for UI validation as well as database design when you use EF Code First with ASP.NET MVC, which is pretty cool.  If data is saved, I am returning “OK.”  As you can see in my javascript above, I am looking for string “OK” and redirecting to the Index action/Url – list of attendees.  Alternatively, I could stay on the same page.  There is a lot of room for clean up on this page, of course.  I could add some animations to play during submit, disable all the controls, save newly created ID into a hidden field for subsequent updates, show error messages, maybe even replace entire Html of the form, replacing OK string with something like return View(attendee) or RenderPartial call.

Thank you.

One Comment

Leave a Reply

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