Sunday, February 24, 2008

UpdatePanel, Who?

I don’t know why people are so enamoured with ASP.NET AJAX.  Sure it works and is developed by Microsoft, but what’s wrong with the dozen other alternatives out there?  Many developers I talk to will say “yeah I used Ajax on this” (with a capital A) and what they are referring to is ASP.NET AJAX.  While I don’t think it is a horrible framework (I use it on occasion), I think it is too complicated and requires too much to get a simple example started.  When people say they have ASP.NET AJAX experience, it means to me that they have experience with declarative controls and <ScriptManager> tags.  It doesn’t necessarily mean they know how ajax works and it definitely doesn’t mean they know anything about javascript.  This worries me.

I often talk about how awesome prototype is (and lately I’ve been digging jquery) and inevitably someone tells me it’s just eaiser to wrap your sections in an UpdatePanel and be done with it.  It is sometimes hard to argue with that, even though it is the most inefficient way of doing ajax.  In addition, this can cause side-effects in WebForms because the entire lifecycle is being executed, even though only part of the page is rendered.  This method is actually called “partial html replacement” or “partial page rendering” and can be achieved easily without needing ASP.NET AJAX.

Now that ASP.NET MVC is out, which doesn’t work with ASP.NET AJAX (not the full stack) people are foaming at the mouth for their UpdatePanel equivalent.  (I think we’ll see an update for this in the coming weeks that will show ASP.NET AJAX working specifically in the MVC environment, but this point still stands.  You don’t have to use it.)

I’m going to use jquery in my sample, but the technique will apply to prototype, mochikit, and others.  In this sample we will take a functioning page and turn it into an ajax enabled page, while preserving the non-ajax scenario (if javascript is disabled).

For our example, I will borrow from an impressive rails tutorial [Quicktime] and implement the same thing in ASP.NET MVC.  The basic concept is this:  Search for flickr photos and display them on the page using ajax. 

We have our page:

Photo-search-form

if you search for “flowers”, you are taken to another action (physically) that shows you the results:

Photo-search-results

Now let’s assume that you want this to happen with ajax.  We’d like the results to be displayed directly on the same page as the query.

Here’s the form from that first action:

<h1>Photo Search</h1>
    <div class="box">
        <form id="search-form" method="get" action="/photos/find">   
            <label for="query">Query:</label>
            <input type="text" id="query" name="query" />
            <input type="submit" id="search" value="search" />
            <img id="indicator" style="display:none" src="../../content/load.gif" alt="loading...." />
        </form>
    </div>

And the action that the form submits to:

[ControllerAction]
public void Find(string query, string format)
{ 
     var flickr = new Flickr(ConfigurationManager.AppSettings["flickr.api.key"]);
     var photos = flickr.PhotosSearch(query, TagMode.AnyTag, query, 40, 1);

     ViewData["query"] = query;
     ViewData["photos"] = photos;

     RenderView("results");
}

(This uses the FlickrNet library available on CodePlex)

In order to translate this into an ajax-enabled page (while preserving no-javascript behavior) we have to do 2 things. 

  • Prevent the form from posting, so that the browser doesn’t take us off the page
  • Make the Find() action render only the partial html for the images (in the case of an ajax call

Let’s solve the first problem first.  Using jquery, we can capture the form submit by doing this:

function init()
{        
    $('#search-form').submit(        
        function() {
            $('#photos').hide();
            $('#indicator').show();  
            hijack(this, loadPhotos, "html"), 
            return false;
        });
}

$(document).ready(init);

When the document has been loaded, init() is called.  This finds our form and adds an onsubmit handler to it.  Next, we hide the photos div (in case there were pictures there already) and then we start the ajax indicator.  For the love of all that is good and holy, let the user know that you’re doing something!  Ok next up is this strange hijack() function.  This guy takes the form and submits it via ajax instead.  It accepts the form, the method to call when the result comes back (callback), and the data type of the return value.  Finally, to cancel the browser from actually submitting the form, we return false.

Here is that “photos” div I mentioned a second ago:  (I put this directly under our form)

<div id="photos" class="box" style="display: none">
</div>

It’s initially hidden because it will be empty when the page physically loads.

hijack() is a generic javascript function, and is defined as:

function hijack(form, callback, params, format)
{   
    try {                                            
        $.ajax({
            url:        form.action,
            type:       form.method,
            data:       $(form).serialize() + "&ajax=true",
            dataType:   format,
            success:    callback,
            failure:    function(e) { alert("error! " + e); }
        });
    } catch(e) { 
        alert(e);
    }
}

This is pretty basic ajax stuff in jquery.  The only thing different that I am doing here is notifying the controller that this is an ajax call, so that we may make it behave a little differently.  After all, we don’t want it to return a full html document… we only want the images!

That’s the essense of issue #2.  If we revisit our controller action, we can make the following change:

[ControllerAction]
public void Find(string query, bool? ajax)
{ 
    var flickr = new Flickr(ConfigurationManager.AppSettings["flickr.api.key"]);
    var photos = flickr.PhotosSearch(query, TagMode.AnyTag, query, 40, 1);

    ViewData["query"] = query;
    ViewData["photos"] = photos;
    
    
    if(ajax.HasValue && ajax.Value)
    {
        RenderView("_images", photos);
    }
    else
    {
        RenderView("results");    
    }       
}

We’ve added an optional parameter for the action to check for.  There are a number of other ways you might accomplish the same thing by embedding this into the controller itself.  You could make a convention that allows a base Controller class set an IsAjax boolean property to true if it detects this variable in form variables or querystring.  Whatever you find the cleanest.

And, Here is our new form!

Photo-search-ajax

You couldn’t see it in that still image, but there was an animated ajax indicator, and a nice slide down effect on the photo div.  If you turn off javascript, the whole thing still works.

This entire process is basically what you get with UpdatePanel.  The same techniques are used to accomplish partial page rendering.  Granted, UpdatePanel works slightly different (it’s parts of the same page that get updated) but you could easily return a full html document from the action and snip out the html you want to update.  Or you might have a specific ViewFactory that knows to only serialize HTML starting with an element root.

With some conventions and a tiny bit of javascript, you could easily achieve partial page rendering with out any fuss.  Plus you get to work with jquery, which is much mor enjoyable than ASP.NET AJAX.

UPDATE:  Download the code below.  You’ll need your own flickr api key (you can get one instantly from flickr.com).  Place it in the web.config.

PhotoSearch.zip (124 KB)

Sunday, February 24, 2008 1:49:36 AM (Central Standard Time, UTC-06:00)
NicE!!!! Now what would be even more awesome, is if you let us download the code!
Sunday, February 24, 2008 8:45:35 AM (Central Standard Time, UTC-06:00)
Hi Ben,

I agree with you 100% that ASP.NET AJAX is way to complicated. I was trying to create a simple Ajax enabled Drag and Drop and had to turn to JQuery for its simplicity and ease of use.

The only reason when Update Panel might be helpful is when you have a complicated data bound control like GridView, DataGrid, DataList which you cannot afford to re-create again on the async postback.

Sunday, February 24, 2008 10:37:53 AM (Central Standard Time, UTC-06:00)
+1 to jQuery. I was using prototype but found it a little awkward to work with. Since I started using jQuery my ajax experience is much better.

I find the syntax easier to understand and more intuitive. In addition to that, the documentation for jQuery and community surrounding it is very strong.

Good post!
Sunday, February 24, 2008 1:05:43 PM (Central Standard Time, UTC-06:00)
@Joe: the code is now available for download.
Ben Scheirman
Monday, February 25, 2008 1:20:11 AM (Central Standard Time, UTC-06:00)
I like you observation about developers who use Ajax (with a capital A) but know very little about Javascript. We've got a few developers who don't really want to touch Javascript (one of them senior) and yet they will put web development as a skill on their CV's.

Sometimes I feel like I want to leave the Microsoft camp for reasons such as these just so that I can keep my sanity.
Monday, February 25, 2008 7:44:48 AM (Central Standard Time, UTC-06:00)
+1 to JQuery; +1 to ASP.NET developers not knowing JavaScript. I also agree with your observation that MS AJAX is overly complex. The AJAX Toolkit is even worse in terms of unnecessary complexity. It looks more like a NASA project.

However, when I look at MS MVC, I see a simple and elegant concept---which has existed for some time now---turned on its head the typical, heavy-handed Microsoft way. What do you think?
Monday, February 25, 2008 8:32:00 AM (Central Standard Time, UTC-06:00)
@Trumpi: I feel your pain, but there's definitely room in this Microsoft community for folks who don't just accept whatever comes from Microsoft. .NET is a fantastic platform, the tools are mostly excellent. We just need more community voice for things that we like or dislike.

@Milan: ASP.NET MVC is a breath of fresh air for me. It is *much* simpler than WebForms and Microsoft has their finger on the pulse of the community on this one. While it may be more complex than Rails (for example) you have to keep in mind that 1) the C# language isn't nearly as flexible as Ruby, so you have to work with what you do have. and 2) Microsoft has a 20+ year reputation of backward compatibility. That 2nd point brings with it a lot of negative baggage, but it's a trade-off they're committed to making.
Ben Scheirman
Tuesday, February 26, 2008 3:36:12 PM (Central Standard Time, UTC-06:00)
I loved the jquery piece. Thanks!
Tuesday, February 26, 2008 4:54:54 PM (Central Standard Time, UTC-06:00)
Have you seen http://ajaxwidgets.com ... ?
A very different approach to Ajax than ASP.NET Ajax, though it still requires the full page life cycle it is very far from ASP.NET Ajax and actually a fully fledged competitor to ASP.NET Ajax...
Wednesday, February 27, 2008 4:22:52 PM (Central Standard Time, UTC-06:00)
What do yo think about using this syntax for specifying your form:

<% using(Ajax.AjaxForm().To("/photos/find").Update("#photo"){ %>
...form elements ...
<% } %>

This will emit the form and wire up the ajax to submit the form and update the #photo element on success in an unobtrusive manner (ie. wired up like you do on the page load)

And use this with your choice of library (actually jquery, prototype or extjs)

This is what we're trying to build with the ClientScript Library. We haven't got the API nailed down yet, but we have acceptance tests for that snippet. I don't have the Submit function callback yet, allowing you to call the hide and show statements during the submit. We'll have to add that.

John
Thursday, February 28, 2008 10:27:56 AM (Central Standard Time, UTC-06:00)
@John : Yes, that's how rails does it and I like it, however at first glance it isn't as intuitive as calling my own method.

Granted, once you understand how it works you can really fly.

but I really dislike the using syntax for forms... it looks messy to me.

what about:

<%=form.Ajax("/photos/find").Update("#photos")%>
...
</form>

I'm not married to that syntax either, but I don't like code blocks that span multiple lines (sometimes they're unavoidable of course, like with loops & if statements).

Ben Scheirman
Thursday, February 28, 2008 7:34:30 PM (Central Standard Time, UTC-06:00)
I'm actually not fond of the using statement either, but I thought I was in the minority on that. your style is the first draft I did. vs doesn't like it, but I'm okay with it.

I agree about being able to call your own method, especially since that snippet does show or hide anything like your example does. We expose the onsuccess event to hook in your own callback function, need to do something similar with OnSubmit.



We're hoping to get a first alpha version out next week. We've got a lot of cleanup and a few more features, but it will have enough for minor ajax / javascript work.

Thanks,
John
Thursday, February 28, 2008 9:52:37 PM (Central Standard Time, UTC-06:00)
Additionally your implementation can provide a very good fallback if JavaScript is disabled or cannot be run due to some reason.

add
<input type="hidden" id="ajax" name="ajax" value="false" />

and change its value to true before performing Ajax request. Otherwise, normal submit will occur with ajax="false"


Can you also "estimate" the same code with ExtJS library? I like it, but have not had opportunity to try?
Friday, February 29, 2008 3:22:00 PM (Central Standard Time, UTC-06:00)
I read "why are people so enamoured with Asp.Net Ajax" but thought I read "why are people so enamoured with asp.net mvc". I am absolutely fanatic about asp.net mvc. Your point about Asp.Net Ajax is very valid, however. Microsoft has a habit of making things over complicated, bloated, inflexible and targeted to the lowest common demoninator. I have never so much as read an Asp.Net Ajax tutorial because I prefer the beauty and structure of prototype (and related) frameworks.

Asp.Net MVC, however, is remarkable. Microsoft paid close attention to what worked in existing MVC frameworks and built something that smacks of beauty and excellent software design.

I was here to find reference urls for a presentation I am giving on nHiberate, as you were responsible for my early training and discovery, and am very please to see you writing a book for MVC. It doesn't surprise me. You are someone to watch, after all.

thanks for everything and keep up the good work
Comments are closed.
Loans - Credit Card - Mortgages - Scottsdale Landscaping