Probably 99.99% of HTML applications and websites are served over HTTP exclusively. (I'm referring here to HTTP as a transport protocol, not HTTP vs.HTTPS, for example, and I realize that HTTP is an application-layer protocol according to OSI; but developers generally treat it as an abstraction for "the network"). As anybody who has done web programming knows, HTTP is a stateless protocol; that is, it's based on a request-response model, and in general, one request has no knowledge of previous requests. This has posed some challenges for web developers over the years, and some brilliant abstractions of state on top of the statelessness have been devised.
The hard part now, though, isn't to deal with statelessness. It's dealing with the request-and-response model.
All network communication is inherently request-and-response. There are some applications that utilize full-duplex communications to get around that (think of chat software), but for the most part, that isn't really available behind the firewall. Web sockets are still yet to be standardized (and there are some questions about long-term compatibility with WebSocket-ignorant proxies). And typically, corporate firewalls say no to outbound connections except on ports 80 or 443. Some applications (think Meebo) have been able to get around this limitation by cleverly using long-timeout delays on AJAX requests. The client makes a request to the server, and the server either responds immediately (if an event is in queue) or holds the request for 30-90 seconds to see if an event comes in. I even did this once myself with good success, although I never took that app into production. (There was also some question about the total # of clients an ASP.NET server could sustain whilst holding threads in that way).
In many respects, Windows developers haven't had to deal with this. We could issue synchronous requests, and the UI would stand still for a second, and either it would work or it would fail. But usability concerns over this process, as well as issues with high network latency (imagine pressing the "Submit" button and having to wait 20 seconds while your app freezes - by then, I've force-closed the app) have seen platform providers decree that asynchrony is the only way to go.
HTML isn't the only application provider dealing with this limitation. Adobe Flash has long had an asynchronous-communication-only model, Microsoft Silverlight has also carried on this principle; of course, these two applications have lived predominantly in browsers, where a hanging UI probably means interfering with other apps as well as the one making the request. Interestingly, WinRT - the Windows 8 developer framework - is also going to mandate an asynchronous model, following in the Silverlight-based foodsteps blazed by Windows Phone 7.
So as we trek out into the world of asynchrony, well, we have a whole mess of questions to deal with now:
- If there's an error, does it show up in the calling method or in the callback method? Does it even show up?
- Does a network (transport-level) error surface differently than an application error? What if the server returned an HTTP 403 Forbidden response?
- What are all of the different kinds of errors that can crop up? Do I need to handle SocketException or is that going to be abstracted to something more meaningful to my application?
- What do I do if a network error comes up? Do I assume that I'm offline, panic, and quit? What if my application only makes sense "online"?
- Do I surface an error to the customer? Silently fail? I might generally fail silently if I'm a background process, but then again, what if it's an important one? What if the customer thought he was saving his draft while all along it was offline, and then the customer closes the browser?
- During the async operation, should I show the user a timeout spinner or something to that effect?
- How should I design my async operations? For example, consider a Save operation. Should I capture all of my state at once and send it off, and let the user immediately keep working? Should I make the user wait until saving completes? Should I even use Save, or automatically save whenever something changes?
- If I use auto-save, how do I handle undo? What if I want to undo between sessions? Is there a way to go back if the hosting application crashes? (Worst case scenario: the user accidentally hit Select All, Delete and then the browser crashed after the auto-save).
- Write a singleton object? This might be easier and afford strong member protection, but I can only have one widget, unless I somehow differentiate between them and multiplex, which can become hairy quickly.
- Should the monitoring function accept a callback, or should it be event-based, so that multiple subscribers can listen? (Maybe an event-based model offers some interesting ways to deal with the complexities of a singleton?)
- Should the widget manipulate the view directly, or should I write separate code that handles the view based on the state of the object (or objects)?
The list goes on.
We're moving faster and faster into an asychronous world. It is already happening, and we as developers need to be prepared to handle these difficulties. We also need to understand how to communicate these kinds of questions to our business analysts, our supervisors, and our customers. We need to be able to equip ourselves to ask the right questions of our customers, so that when it's time to make a decision, we have the information we need.
This morning I received an email that posed a question so interesting that I thought I would blog about the answer. The question was, essentially, how can we invoke a partial update (using ASP.NET AJAX triggers), from an on(up) button handler in Flash?
There are a few different ways to approach this problem. I believe the method I’m going to write out here is what I like to call the “path of least resistance” – it’ll get you there quickly. However, it will create some interdependencies among your controls. However, using this technique as a baseline, you should be able to adapt it to fit other better techniques, which I’ll describe later.
I won’t get into the details of how to access script with ActionScript; I’ll simply leave it to the professionals (and use this as an example). With this capability we should have everything we need to build an AJAX-enabled Flash button. I’ve created a sample ASPX page that will host what we need:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head runat="server"><title></title></head><body><form id="form1" runat="server"><div><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="150" height="60" id="blog" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="blog.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#ffffff" /><embed src="blog.swf" quality="high" bgcolor="#ffffff" width="150" height="60" name="blog" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /></object><asp:ScriptManager ID="sm" runat="server" EnablePartialRendering="true"></asp:ScriptManager><asp:UpdatePanel ID="update" runat="server"><Triggers></Triggers><ContentTemplate><asp:Label ID="lblText" runat="server" Text="Click the Flash button to turn me blue." /></ContentTemplate></asp:UpdatePanel></div></form></body></html>
We still need a way to invoke the partial update. Unfortunately, what I termed the “path of least resistance” is going to involve a little voodoo: we’re going to create a Button, set its display to none (so that it’s on the page but hidden), and then treat it as a trigger for the UpdatePanel:<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="150" height="60" id="blog" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="blog.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#ffffff" /><embed src="blog.swf" quality="high" bgcolor="#ffffff" width="150" height="60" name="blog" align="middle" allowScriptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /></object><asp:Button runat="server" style="display: none;" ID="btnFlashGo" /><asp:ScriptManager ID="sm" runat="server" EnablePartialRendering="true"></asp:ScriptManager><asp:UpdatePanel ID="update" runat="server"><Triggers><asp:AsyncPostBackTrigger ControlID="btnFlashGo" EventName="Click" /></Triggers><ContentTemplate><asp:Label ID="lblText" runat="server" Text="Click the Flash button to turn me blue." /></ContentTemplate></asp:UpdatePanel>
We’re still missing one piece: we need to generate the function call to actually make a postback. There’s a relatively convenient way to do that, using the ClientScriptManager; drop this Literal onto the page:<asp:Button runat="server" style="display: none;" ID="btnFlashGo" OnClick="btnFlashGo_Click" /><asp:Literal ID="flashScript" runat="server" />
This setup should give us everything we need to make the AJAX call, and sure enough:
I would be happier – much happier – with this solution if it didn’t depend on so much black magic. There are a few ways to get it to work better, and while I don’t want to take the time to demonstrate them here, I can describe them a bit.
A FlashButton Control
In my mind, a FlashButton control is the best solution; it can derive from Control or WebControl (although to be honest simply Control is preferable), and can automatically do the heavy lifting. You could incorporate SWFObject as a script resource and provide it automatically. FlashButton could expose its own events, which means that you could eliminate that Button (the hidden one) and instead create the script callback reference pointing to the FlashButton’s event itself (and the UpdatePanel’s trigger could point there as well).
A FlashButtonManager Control
A FlashButtonManager could extend the support for FlashButton much like ScriptManager does for UpdatePanel. While the approach with a single FlashButton works well when only a single FlashButton is on the page, incorporating multiple FlashButton objects becomes tricky when you factor in things like handling multiple callbacks (for instance, naming the flashClicked() function). A FlashButtonManager could be designed such that it handles each flashButton on the page, perhaps setting up FlashButtons with a FlashVar to specify a parameter when calling flashClicked(), and then using that parameter to determine which one was clicked and firing the appropriate postback event.