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)

Saturday, February 23, 2008

ASP.NET MVC in Action - Early Access PDFs

book coverThe cat is out of the bag now!  I’m writing a book on ASP.NET MVC for Manning Press entitled ASP.NET MVC in Action.  I really thrilled to be part of this along with my co-authors Jeffrey Palermo and Dave Verwer.

So far 3 chapters are available on Manning’s Early Access Program.  We’d love to hear your feedback on the book so far.

Writing a book is much harder than I originally thought, but I think that blogging for four years has definitely helped.  With a book, everything has to be much more “polished,” so finishing up chapters tends to take a while.  This might explain my drop in posting frequency, but I hope to add tidbits of information that I’m working on to give you guys a peek at what the book will be like.

Our target release date will be October 2008.

Thursday, February 21, 2008

More Downtime - Local Activation Permissions on DCOM

What luck.  My server went down last night and didn't come back up until this morning.  I eventually had to request a power cycle from my host and then remote desktop in to see what went wrong.

The root cause of the error was this:

The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID ...... to the user NT AUTHORITY\NETWORK SERVICE.

This caused a few other errors to occur in rapid succession and Windows decided to shut down my application pool.  You can clearly see the issue here:




I read up on the error and found a solution.  Apparently after Server 2003 SP1 came out, this issue started appearing on servers with a specific Group Policy (I'm not sure what).  They suggested to add NETWORK SERVICE to the DCOM Users group.

So far it is working fine.  We'll see!

I wonder how much google juice I lost during the 12 hour outage.  :(
Tuesday, February 19, 2008

Yikes! I Spoke Too Soon!

I previously noted that I was outgrowing shared hosting and I migrated to a VPS account with VPSLAND. 

I spoke too soon.  Their prices are the lowest for a reason.

VPSLAND’s support was good for the first 24–hours, but after that they neglected me.  I opened a critical ticket because my sites (including this blog) were down for over an hour.  I eventually fixed it on my own by some strange combination of disabling services and rebooting the server 3 or 4 times.  6 hours later a tech chimes in and says “we checked your sites and everything is fine!”  — yeah, great help.

servers

I inquire as to why the system won’t even serve up an empty subtext application and they told me that I should upgrade to a 1GB RAM plan (from 512mb).  I figured I’d need it eventually, so I obliged.  It took them 12 hours to respond to my request.  Meanwhile, one of my sites was acting sluggish and I couldn’t get my friend’s blog started b/c subtext kept timing out.

The next morning they told me my server was upgraded and everything was fine.  But it wasn’t.  I was seeing the same sluggish behavior and subtext was continually timing out (at the database level — but remember, this is an empty database).  This time I know it’s not RAM because I have 700 freakin megabytes free.

I eventually broke down and got a VPS plan at godaddy (512MB ram) for $37/month.  It’s a little more money, but they set it up in about 45 minutes and it’s worked flawlessly so far!

I also want to apologize to a certain reader who decided to go with VPSLAND after reading my post.  He is also having troubles with them and I wish I had not posted such a glowing review so quickly after signing up.  VPSLAND refunded my payment, so hopefully if you still have problems you can do the same.

Anyway, I will say that so far godaddy’s plan is good, but I will not give them my Seal of Approval (TM) until I’ve used it for a couple months.

(if you noticed the downtime, I apologize!)

Thursday, February 14, 2008

Instilling Quality in Your Teams

Quality is important.  I think that deserves repeating.  Quality is important.  Quality of code, quality of process, quality of requirements, quality of communcation, quality of behavior, quality of teams, quality of customers.  I tend to focus on the code side of things, but it is always important to remember that quality matters.

Often times (due to lack of quality in some of the other areas), quality can get pushed aside in the interest of getting things done.  This is a balance that all of us must deal with.  Ensuring good communication can put out fires long before they blow up in your face.  Quality of code can ensure that future maintenance and development goes on without slowing to a crawl in productivity.  Quality in process can guide you automate the tedious repeatable tasks to streamline your work.  Any breakdown in these areas can be catastrophic to a team.

We had a meeting last night at Sogeti, and quality kept coming up as a topic.  I was very proud to see the end result of the meeting:



Wednesday, February 13, 2008

A New Host

I've been hosted on webhost4life for about 2 years now, and I was starting to see the pain with shared hosting.  It starts out with a tempting $5/month, but then you want SQL Server 2005, and you want to point many domains to non-root folders and in the end it just isn't that cheap.

It's also really slow and I don't have full control of the box.  Doing a reverse ip check at http://yougetsignal.com (awesome site, hilarious name) i could see hundreds of sites that are on my same server.  I'd rather not be sharing bandwidth and hardware resources with so many.

So I've made the switch to a VPS plan at VPSLand.  VPS is Virtual Private Server, so basically they take a beefy server (8-core cpu, gobs of ram) and then run Virtual Server on it.  They sell a limited amount of virtual machines per actual server, so you are sharing with far less people. VPS plans are a bit more expensive, but they are worth it.  Why you ask?
  • Remote Desktop into your box (no more crappy cpanel interfaces)
  • Install whatever-the-hell-you-want software (provided it's legal)
  • Run your sites in .NET 2.0, 3.0, 3.5, 6.0, whatever (ASP.NET MVC sites here I come)
  • Run ASP.NET, Rails, PHP all on the same server (if you want)
  • No more Medium Trust nonsense
  • control your own DNS (if you want)
  • run your own mail server (I use GAFYD however)
  • point as many domains to whatever folders you want
The downside:
  • I have to track my bandwidth now.  I have 200gb/month, which is good enough for now, but it could get expensive if I go over.
  • I only have 5gb of storage space.  The base install takes 2gb.  Yikes!  I plan on making this 10gb sometime soon.
  • a little more expensive.
Ultimately, I'm paying about $10/month more than shared hosting, and I think it's totally worth it.  VPSLand's support is stellar so far.

Oh, and I have a question for you all:  I plan on tinkering with rails, and I've read the long steps to get rails to work in IIS but should I?  Or should I just install apache or Lighttpd and run it on port 81?
Tuesday, February 05, 2008

Visual Studio Context Menu - The Himalayas of Menus

This is just ridiculous:

Vs-context-menu

I could seriously eliminate about 10–12 items from there and never miss them again.  For the others that I use occasionally, they would be better served on a top menu, not when I right-click.  All of the source control things should be grouped in a sub-menu or something, because this thing is just way too crowded.

Any way to control this chaos?

Wednesday, January 30, 2008

Testing TempData in ASP.NET MVC

Eventually when testing your controllers you will come across an action that sets TempData.

If you didn’t know, TempData is a session-backed temporary storage dictionary that is available for one single request.  It’s great to pass messages between controllers.

Our Controller classes that we derive from have this as a member property.  However, when testing the controller, this always came up as null.

Reflector is our friend here, so I fired it up and loaded the Controller class.  Then I located the TempData member, and looked at the setter.  It’s marked as internal, so somebody inside this assembly is responsible for creating it.  By using the Reflector Analyze feature, I was able to find exactly where the setter is called.  It’s in the Execute() method.  (this is the general “pipeline” method that all IController implementations must override)

Think about that for a second.  At runtime, the TempData dictionary is set because all requests flow through Execute().  But in my test, I am specifically calling my action method.  There is no Execute() in my test.

To top it off, I can’t set this guy to a new TempDataDictionary because the setter is marked as internal.

Phil Haack tells me they’re working on this issue so we’ll likely see an improvement soon.  In the meantime you can get around the issue by using reflection to set the value.  But first, can we create one?

TempData is stored in session, so it needs a reference to IHttpContext.  Thankfully, we can mock this, however we have to make sure and mock the right parts.

First, create a mock for IHttpContext, create the return values for mocked IHttpRequest & IHttpResponse.  Finally setup return values for IHttpSession as well.  I’ve done all of this in a helper method of my test (though it could be an extention method on the MockRepository ala Phil, but whatever you like).

private IHttpContext GetHttpContext(string requestUrl)
{            
	IHttpRequest request = _mocks.DynamicMock<IHttpRequest>();
	SetupResult.For(request.Url).Return(new Uri(requestUrl));
	IHttpResponse response = _mocks.DynamicMock<IHttpResponse>();
	IHttpSessionState session = _mocks.DynamicMock<IHttpSessionState>();
        IHttpContext httpContext = _mocks.DynamicMock<IHttpContext>();

	SetupResult.For(httpContext.Session).Return(session);
	SetupResult.For(httpContext.Request).Return(request);
	SetupResult.For(httpContext.Response).Return(response);
        _mocks.Replay(request);
        _mocks.Replay(response);
        _mocks.Replay(httpContext);

_tempData = new TempDataDictionary(httpContext);
SetupResult.For(session[null]).IgnoreArguments().Return(_tempData);
_mocks.Replay(session);

 return httpContext; }

This code is a little verbose, but this is something you write once and use in all of your tests. You should notice that the _tempData property is a local field on my test class. I can then take this _tempData variable and inject it (via reflection) onto the controller under test. Here's how:

typeof(FooController).GetProperty("TempData").SetValue(controller, _tempData, null);

And now we can easily test that messages are set in TempData before redirecting or rendering a view.

*update*:  Will Shaver notified me of a typo in the code.  It has been fixed.  Thanks Will!*

Remortgage - Loans - Credit Card Consolidation - Credit Counseling