Friday, October 19, 2012

AspQ - A JavaScript Event Queue for ASP.NET

A nearly universal problem in ASP.NET is handling multiple postbacks. There are various server-side and client-side solutions to deal with this, but these solutions can only be understood once you already have the domain knowledge to solve the problem yourself. You then end up repeating the pattern in every project.

Well, programming is about not repeating yourself and automating repetitive tasks. So I've released a reusable abstraction to handle multiple postbacks on the client-side. Because it's a client-side solution, it will only work for browsers with JavaScript enabled, but that's by far the most common case.

AspQ

AspQ is a small JavaScript object that hooks into the ASP.NET JS standard and AJAX runtimes. It basically queues all sync and async postback requests so they don't interfere with one another, and applies the updates in event order. I believe this option provides a superior user experience to simply preventing a submit until the previous postback has completed, because the user can still operate on the UI while they wait.

There have been quite a few incarnations of this idea in the wild, but I've found them all lacking. Either they didn't work for LinkButtons, or they didn't work with Master pages, or they didn't handle sync postbacks. AspQ should do them all.

So go ahead and download AspQ.js from the repository and save it in your site's script directory. You then have a choice of the following two methods to use it.

Method 1

Just include the JS in your pages:

<script type="text/javascript" src="/path/to/AspQ.js"></script>
And place the following server-side code in your OnInit method of the System.Web.UI.Page:
override protected void OnInit(EventArgs e)
{
  ...
  // register form submission script
  this.ClientScript.RegisterOnSubmitStatement(GetType(),
    "PreventDuplicateSubmits", "return AspQ.submit(this);");
  ...
}

Method 2

This method makes only server-side changes using similar changes to the OnInit method:

override protected void OnInit(EventArgs e)
{
  ...
  // include script site-wide
  var script = new HtmlGenericControl("script");
  script.Attributes.Add("type", "text/javascript");
  script.Attributes.Add("src", "/path/to/AspQ.js");
  this.Header.Controls.Add(script);

  // register form submission script
  this.ClientScript.RegisterOnSubmitStatement(GetType(),
    "PreventDuplicateSubmits", "return AspQ.submit(this);");
  ...
}

AspQ is simple to setup and seems adequate for most purposes. If you have a use case that it doesn't handle, please let me know!

It's released under the LGPL, which is my default OSS license, but I'm open to discussion on the issue since the LGPL may not be appropriate for a JavaScript library.

Edit: here's a demo showing a simple counter. Press the +/- buttons as many times and as fast as you like, the updates will all happen and in the proper order.

4 comments:

davidgrupp said...

could you please provide a demo and maybe some samples of this? seems interesting but i'm not exactly sure what it's doing. also is there a minified version and un-minified version?

Sandro Magi said...

I haven't minified since that's pretty easy to do with an online tool (just copy-paste).

As for a demo, here's a simple counter program where I've added a 1 second delay while processing each async button click. You can press +/- rapidly as many times as you like, and those requests are queued up and processed one after another.

The reset button is a full postback that resets the counter. You can see the full source here.

niallt said...

Hi there,

this looks like a good solution to the problem we are having with multiple postbacks. However, it does mean that client side validation doesn't happen. Do you know of a solution which allows client side validation to happen?

Sandro Magi said...

I don't use client side validation much since it's relatively easy to bypass, so I doubt I'll address that. I'm not aware of any other off the shelf solutions. Server-side postback solutions would work for you in this case.