Wednesday, July 30, 2008

A Routing Evolution

216624fork-in-road-posters When the ASP.NET MVC framework was first released, two open source projects sprung up immediately:  MVCContrib and Code Camp Server.  I quickly jumped on Code Camp Server as a lead contributor because I wanted to get some hands on experience on the ASP.NET MVC Framework.

In the first example you see when you install the new MVC templates, you see that the default routing rules are defined like this:

{controller}/{action}/{id}

This works well for a lot of applications, where you'd end up with URLs like:

/customers/list
/customers/delete/12
/products/show/5
/products/edit/5

It's quite easy to see how each of those map to the components of the route.  In Code Camp Server, we wanted the system to support many code camps, so your URL will look like this:

/austinCodeCamp/details
/houstonTechFest/directions

So we ended up with a default route that looked like this:

routes.MapRoute("conference", "{conferenceKey}/{action}", new { controller="conference", action="details });

Here we omitted the controller as it was implied.  Everything was placed on ConferenceController, and it worked pretty well to start out.  Then we started adding actions like Sessions, and ListAttendees, and started to notice that those things probably belong on their own controllers. 

But we had a problem... if I have a URL like this:

/houstonTechFest/sessions

this will translate to an action called "sessions" on the conference controller.  That's not what we want.  So we started to hard code these specific instances like this....

routes.MapRoute("speakers", "{conferenceKey}/speakers/{action}", new {controller = "speaker", action = "list"});
routes.MapRoute("schedule", "{conferenceKey}/schedule/{action}", new {controller = "schedule", action = "index"});
routes.MapRoute("sessions", "{conferenceKey}/sessions/{action}", new {controller = "session", action = "list"});
routes.MapRoute("sponsors", "{conferenceKey}/sponsors/{action}", new {controller = "sponsor", action = "list"});

YUCK.  Things are starting to get really hairy now.

So, after a phone call with Jeffrey Palermo, he raised the question:

"Why even have the default point to conference controller?  The only common action on that is Details.  All of the rest are separate controllers."

And then it clicked.  We can now change the route definition to this:

routes.MapRoute("standard", "{conferenceKey}/{controller}/{action}/{id}", new { controller="conference", action="index", id=(string)null});

Which produces these very acceptable URLs:

URL Controller Action Id
/houstonTechFest Conference Index  
/houstonTechFest/sessions Sessions Index  
/houstonTechFest/sessions/add Sessions Add  
/houstonTechFest/sponsors/edit/5 Sponsors Edit 5

there are a few extra cases where we don't want to start with a conference key (for example, /conference/list, /conference/current, /login, /admin, etc...).  These URLs all work with the standard route, so we can define that just below our first route.

routes.MapRoute("conference", "{controller}/{action}/{id}",
                new {controller="conference", action="list"});

Now the URL /conference/list (and the others) will get routed properly with this definition. 

Now there's just one final problem.  Can you see it?  If we define the other route first, then the first token of the route will get picked up as a conference key!  We certainly don't want that.  So we'll add a constraint to the first route to match everything except the controllers we want to address specifically.  Our route now looks like...

routes.MapRoute("confkey", "{conferenceKey}/{controller}/{action}/{id}",
    new { controller="conference", action="index", id=(string)null },
    new { conferenceKey="(?!conference|admin|login).*"});
            
routes.MapRoute("standard", "{controller}/{action}/{id}",
    new { controller="conference", action="index", id=(string)null });

The first route will grab everything except those URLs that start with "conference", "admin", or "login."  There might be other controllers, but this is all we need for now.  Those URLS get picked up by the 2nd route and everything works fine after that.

Coming up with a clear set of routes that don't conflict with each other is very difficult once you stray away from the norm.  The more route definitions you have, the more care you have to take to ensure that the new rule doesn't break a whole slew of existing URLs.   This is critically important if you already have an application in production.  A simple change in the Global.asax file can change all of the URL's for your application!  That's like paraquat for Google Juice.

Testing the Routes

Unit tests can surely help here.  For these examples, I followed a simple technique for testing my routes.  First is the method to fake out the actual request:

private static RouteData getMatchingRouteData(string appRelativeUrl)
{
    RouteTable.Routes.Clear();
    var configurator = new RouteConfigurator();
    configurator.RegisterRoutes();

    RouteData routeData;
    var mocks = new MockRepository();           
    var httpContext = mocks.DynamicMock<HttpContextBase>();
    var request = mocks.DynamicMock<HttpRequestBase>();

    using (mocks.Record())
    {
        SetupResult.For(httpContext.Request).Return(request);
        mocks.Replay(httpContext);
        SetupResult.For(httpContext.Request.AppRelativeCurrentExecutionFilePath)
            .Return(appRelativeUrl);
        SetupResult.For(httpContext.Request.PathInfo)
            .Return(string.Empty);
    }

    using (mocks.Playback())
    {
        routeData = RouteTable.Routes.GetRouteData(httpContext);
    }

    return routeData;
}

Now we can have a simple helper method for asserting that are routes produce the right tokens:

private void AssertRoute(string virtualPath, string expectedController, string expectedAction, 
    IDictionary<string,string> expectedTokens)
{
    var routeData = getMatchingRouteData(virtualPath);

    Assert.That(routeData.GetRequiredString("controller"), Is.EqualTo(expectedController));
    Assert.That(routeData.GetRequiredString("action"), Is.EqualTo(expectedAction));
    foreach (var pair in expectedTokens)
    {
        Assert.That(routeData.GetRequiredString(pair.Key), Is.EqualTo(pair.Value));
    }
}    

And finally the tests that I'm running to ensure these routes are working properly...

[Test]
public void TestSiteRoutes()
{
    AssertRoute("~/austinCodeCamp2008", "conference", "index",
    new Dictionary<string, string> {{"conferenceKey", "austinCodeCamp2008"}});
    
    AssertRoute("~/login", "login", "index");
    AssertRoute("~/conference/new", "conference", "new");
    AssertRoute("~/conference/current", "conference", "current");
    AssertRoute("~/admin", "admin", "index");
    AssertRoute("~/houstonTechFest/sessions/add", "sessions", "add",
        new Dictionary<string,string> {{"conferenceKey", "houstonTechFest"}});
}

This is only half of the story though.  You should also test that the routes you ask for (like with Html.ActionLink and Url.Action) produce the intended URLs.  Otherwise you may have URLs that match routes, but routes that don't match your intended URLs.

Testing these is a bit harder, and it's 1am now... so I'll take the ultimate cop-out and leave it as an exercise to the reader.

Sunday, July 27, 2008

Google Reader Quick Tips

#1 – Read Upwards

I like to read my feeds grouped by feed, oldest first.  Scroll down to the last post in the list and press “K” to read upwards from there.  “J” does the opposite & reads downwards.  This is especially useful for busy feeds like Slashdot or Lifehacker, where there are easily 50 posts to read in a day.

#2 – Next/Previous Feed

When using the method above, it’s useful to use Shift+N or Shift+P to navigate to next previous subscription.  This keeps your fingers on the keyboard instead of reaching for the mouse.

#3 – More Screen Real-Estate

If you need more space for reading, press “U” to hide the left column.  Pressing “U” will get it back again.

#4 – Open In-line

If you want to comment on a post (or just click through to see what the site looks like), you can open the post’s original site within the google reader pane.  Just CTRL-Click the title of the post.

#5 – Ninja Mode

Up, Up, Down, Down, Left, Right, Left, Right, B, A.

No, I’m not kidding on this one.

#6 – On-screen help

Press Shift+? to see a nice layout of all the keyboard shortcuts available to you.

 

What’s your favorite Google Reader tip?

Saturday, July 26, 2008

Controller is NOT the New Code-Behind

One of the things that I often say when touting the benefits of ASP.NET MVC is clean separation of concerns.  I talk about how the code-behind of WebForms is just begging you to put code there.  Samples online frequently open direct database connections and execute ad-hoc queries against the database from a button-click event.

Of course the same is possible with ASP.NET MVC.  Just taking a peak at the new Preview 4 installer, you’ll see a brand-spanking new AccountController, pre-built for managing logins/membership on your site.

Open AccountController.cs and you’ll find this method:

    [Authorize]
    public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
    {

      ViewData["Title"] = "Change Password";
      ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;

      // Non-POST requests should just display the ChangePassword form 
      if (Request.HttpMethod != "POST")
      {
        return View();
      }

      // Basic parameter validation
      List<string> errors = new List<string>();

      if (String.IsNullOrEmpty(currentPassword))
      {
        errors.Add("You must specify a current password.");
      }
      if (newPassword == null || newPassword.Length < Provider.MinRequiredPasswordLength)
      {
        errors.Add(String.Format(CultureInfo.InvariantCulture,
                 "You must specify a new password of {0} or more characters.",
                 Provider.MinRequiredPasswordLength));
      }
      if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
      {
        errors.Add("The new password and confirmation password do not match.");
      }

      if (errors.Count == 0)
      {

        // Attempt to change password
        MembershipUser currentUser = Provider.GetUser(User.Identity.Name, true /* userIsOnline */);
        bool changeSuccessful = false;
        try
        {
          changeSuccessful = currentUser.ChangePassword(currentPassword, newPassword);
        }
        catch
        {
          // An exception is thrown if the new password does not meet the provider's requirements
        }

        if (changeSuccessful)
        {
          return RedirectToAction("ChangePasswordSuccess");
        }
        else
        {
          errors.Add("The current password is incorrect or the new password is invalid.");
        }
      }

      // If we got this far, something failed, redisplay form
      ViewData["errors"] = errors;
      return View();
    }

That’s nearly 70 lines in a single method.  Even with the comments it’s difficult to see what it’s doing at a glance.

This serves as a lesson to those of us that sell TDD as a design practice.  Sure this method is testable, but it seems as though the author did not take a few minutes to refactor code.  Normally I wouldn’t pick on someone for writing this, but when this is installed as a project template, it serves as an example for others to write code just like this.

Let’s see how we might clean this code up to make it more readable.

Here’s a good first attempt.  This took all of about 5 minutes to accomplish.

    [Authorize]
    public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
    {
        ViewData["Title"] = "Change Password";
        ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;        // Non-POST requests should just display the ChangePassword form 
        if (Request.HttpMethod != "POST")
        {
            return View();
        }
            
        var errors = new List<string>();
        ValidatePassword(currentPassword, newPassword, confirmPassword, errors);
        if (errors.Count == 0)
        {                
            const bool isOnline = true;
            var currentUser = Provider.GetUser(User.Identity.Name, isOnline);
                
            if( PerformPasswordChange(currentUser, currentPassword, newPassword))
            {
                return RedirectToAction("ChangePasswordSuccess");
            }
                
            errors.Add("The current password is incorrect or the new password is invalid.");               
        }

        // If we got this far, something failed, redisplay form
        ViewData["errors"] = errors;
        return View();
    }

    private bool PerformPasswordChange(MembershipUser user, string currentPassword, string newPassword)
    {
        try
        {
            user.ChangePassword(currentPassword, newPassword);
            return true;
        }
        catch
        {
            return false;
        }
    }

    private void ValidatePassword(string currentPassword, string newPassword, string confirmPassword, ICollection<string> errors)
    {
        if (String.IsNullOrEmpty(currentPassword))
        {
            errors.Add("You must specify a current password.");
        }

        if (newPassword == null || newPassword.Length < Provider.MinRequiredPasswordLength)
        {
            errors.Add(String.Format(CultureInfo.InvariantCulture,
                  "You must specify a new password of {0} or more characters.",
                  Provider.MinRequiredPasswordLength));
        }

        if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
        {
            errors.Add("The new password and confirmation password do not match.");
        }
    }

This code (while still completely in the controller class) is broken apart into distinct methods.  This reduces the line count on the action method to 26, which is much easier to swallow.  I’m sure you see even more opportunities for improvement here.  Some of this code probably belongs in another, completely separate class (one that is even more easily tested that this).

My point is, always remember to include time for refactoring.  Especially when the result of your work will stand for thousands to learn from.

Wednesday, July 23, 2008

.NET Dojo - Free Hands On Training for ASP.NET MVC

dojo 

I'm a little late on this notice (luckily J Sawyer already posted about it) but I'm going to be hosting a .NET Dojo class at the Microsoft office in Houston.

The class will be a free half day session where we will dig into ASP.NET MVC.  I'll cover the basic principles of the MVC pattern, why you might want to use it over WebForms, how to leverage Ajax and how to apply test driven design practices with the framework.

You'll need:

When?

August 28, 2008
1pm-5pm

Where?

Microsoft Office
2000 W Sam Houston Pkwy

How do I register?

http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032383675&Culture=en-US

Space is limited, so first come first served!

Friday, July 18, 2008

Upgrading Code Camp Server to Preview 4 - Part 2

Last time I went through some steps to upgrade Code Camp Server to the latest version of the ASP.NET MVC Framework.  I had to stop because MVCContrib hadn't been upgraded yet.  Now that their 4.0 release is out, I can continue.

Final Changes

I updated the mvccontrib binaries in our /bin folder and then ran all of the tests.  All of the tests passed.  Running the site I came to find a few errors in our views that referenced ComponentController (which should have been caught with tests... Note to self:  precompile the site in the NAnt build...).

 

....And the site is alive again!

site-live

We're now done with the upgrade, all tests are passing, and we need to commit our final changes to the branch we created in part 1.

svn-commit

Now we're ready to merge our changes back into the trunk. 

Merging the branch back to the trunk

To do this we will switch back over to the trunk with TortoiseSVN.

switch-back-to-trunk

Next, we open the Merge dialog from within the trunk.  With TortoiseSVN you're always merging from another branch to your current working copy.

TortoiseSVN asks you how you want to merge your changes.  What we've done is directly described in the second option, so we'll choose that.

merge-options1

Next we select the branch from where we'd like to pull the changes...

merge-options2 

The last dialog has some other options for you, but the defaults will be fine for us.

image 

Now click on merge & cross your fingers!

merge-error

Doh!

We get an error because googlecode doesn't yet support subversion 1.5.  Well that sucks, who planned this blog post anyway?

Time to drop down to command line to do the merge.  That's right, don't be afraid of the command line.  The first thing to do is to find out the revision that the code was branched.  You can do that with a command on the branch working copy itself, by typing:

svn log --stop-on-copy

or I can cheat and look at the screen shot of the merge in part 1.  I can see that the revision is 265.  Now, back on the trunk I can type:

svn merge -r 265:HEAD href="http://codecampserver.googlecode.com/svn/branches/mvcpreview4upgrade

The result:

cmd-line-merge

Running the tests again just to make sure everything worked, and it does!

Now to commit our changes into the trunk...

svn commit -m "integrating preview4 branch"

...And we're done!

I hope that my play-by-play upgrade has been helpful.  Dealing with frequent updates to the projects we use is a price that we pay for the rapidly changing environment that we live in.  Now, off to play with Preview 4 new features and hopefully get to delete some of our newly obsoleted code!

Wednesday, July 16, 2008

Upgrading Code Camp Server to Preview 4

I'm currently upgrading Code Camp Server to Preview 4, and I thought it might be useful to some to see how to go about managing a (somewhat) volatile upgrade to a project.

Step 1: Create a branch for your work

You don't want these changes to break the build or anyone else's pending changes.  Often times in a long, complicated upgrade there will be many parts that need fixing and you can't do it in one sitting.  Creating a branch for your changes isolates you from the trunk.

First this to do is make sure your local working copy is up to date and has no pending changes.  I created a branch called mvcpreview4upgrade and switched my working copy to that branch.

mvcpreview4upgrade-branch

Now we're free to make some breaking changes.

Step 2:  Upgrade the binaries to the new version of the framework

In Code Camp Server, all external dependencies are placed in the bin folder:

upgrading-binaries

All of the projects get their references from here.  We will definitely have to update MVCContrib to their latest once it is upgraded to Preview 4.  I'll leave this to last.

Step 3:  Get it compiling

Normally I'd suggest the command line NAnt build because it will run all the tests, but I'm fully expecting compile errors and I want to be able to double click on them and navigate directly to it.

Here are the issues I notice immediately:

  • ComponentController has been removed completely.  This hasn't been updated in a long while and we kind of expected this to happen.  Unfortunately we had 2 components that used this, so we have to re-implement them using another mechanism (I think I see one already, but I need to investigate more).
  • TempDataDictionary now has a default constructor, so we don't have to create a fake http context to build it.  Finally!

That seems to be it, I fixed these issues and it compiles.  Now to run the NAnt build (with all tests).

Step 4: Get the tests passing

Note to self:  need to upgrade all the old mocking syntax to the new RhinoMocks syntax.

nant-build

We can see that a unit test is failing (and we can also see a couple tests that I'm ignoring for now because ComponentController was removed).

Since the FlashMessageComponent needs to be changed anyway, I'll just ignore this test for now until we can finish the upgrade.

Another test was failing in the IntegrationTests assembly which was fixed by deleting this line:

test-resolving-component-controller

This test is used to make sure that we can build all controllers (with dependencies) at runtime.

After fixing this we are now all passing!

build-succeeding

Now it's time to take a look at the changed features...

Step 5:  Implement the new changes

Browsing the readme that is included with the release, we can see some changes in the following areas:

  • New Membership
    • not sure if we want to take advantage of this since we've already implemented it
  • Routes
    • They are now namespaced to avoid issues with reflecting over the entire assembly
  • AuthorizationFilter
    • I can remove the custom one we have
  • ErrorHandling Filter
    • We were going to end up using [Rescue] from MVCContrib, but now I think we can use this
  • OutputCache Filter
    • This will be better used once we have the functionality completed and we know where the best places to implement caching will be
  • Ajax
    • These look like ASP.NET Ajax improvements.  Not sure if they'll apply to us since we use jquery.
  • ActionInvoker
    • GetFiltersForAction method
      • We can probably better test the integration of custom action filters with this
  • ViewData
    • Eval - we'll probably just stick to ViewDataExtensions from MVCContrib for strong typing
  • ComponentController has been removed in favor on a RenderAction method on the view
    • this means we can probably replace the sponsorcomponent controller with a standard Controller implementation

Updating routes:

updating-route-namspaces

I removed another remnant of ComponentController (looks like this came from MVCContrib).  I also added the new namespace reference here.

For authorization we had previously created 2 attributes, [AdminOnly], and [RequireLogin].  RequireLogin will be replaced with the new [Authorize] attribute, however our AdminOnly attribute works a little differently, so some more work will be needed to get this to work with just a role name.

Our implementation won't be complete until we get MVCContrib integrated, so that'll have to wait.  But for now we're at a good place to commit our work!

Why go through all this pain?

Upgrading projects to frequently released dependencies can be a pain, but I think it's a small price to pay for the constant flow of improvements we're getting from ASP.NET MVC releases.  When the framework settles a bit more these updates will be mostly topical... meaning they won't sweep the carpet out from under your feet.

ASP.NET MVC Preview 4 Available

This will be 1 of 4 billion posts you read on this subject, but ASP.NET MVC Preview 4 is now available.  Read Scott Gu's initial notes and then go download it.

Over the next few days I'll be working on converting CodeCampServer and a few other small projects to preview 4.  Good stuff!

Monday, July 14, 2008

Book Review - Agile Estimating and Planning

I finally finished Mike Cohn's book, Agile Estimating and Planning.  It has been on my reading list for quite a while, but I just finished it last week.  In short I found it to be very helpful.  To understand how, I'll note some of my background in Scrum.

When I first read Agile Project Management with ScrumI was very intrigued.  I found that Scrum melded well with the mostly engineering-focused XP practices that I had previously adopted.

I then took the Certified ScrumMaster course, and I was even more impressed.  The problem, though, was that the course is only 3 days.  3 Days just isn't enough time to consider anyone a "master" of scrum.  It takes years of attempts, trying different things, failing, and overcoming those failures to eventually become  a "master."  But that's a topic for another day.  My point is, that there was still a lot for me to learn.

I gave some internal training on scrum and noticed that there were a lot of questions that I didn't have the answer to (how absurd of me to think that I had all the answers!).  Some of them were practical questions... like what's the best way to determine your iteration size, or how to effectively do release planning.  I knew the concepts, and I knew their purpose, but I struggled with the actual implementation.

After reading Agile Estimating and Planning, I am much more prepared to answer those questions (and implement them in my daily work).  Mike talks in depth about why fixed iteration length is important (which is a question that has come up twice in my project).  He also outlines how to effectively plan an iteration, and what to do if you have too much work planned. 

A co-worker called me once and asked me, "What happens if you partially implement a story during a sprint?" -- the answer came directly out of this book.  The short answer is that you can either move the entire story over to the next iteration (because it isn't done) or you can decide to split the story in two (however this method doesn't work if you implemented functionality but didn't test, for example).

While reading this book I have cracked it open three or four more times to refer to specific advice, which is why I think so highly of this book.  Once you understand the Scrum principles and have a healthy bank of questions about its implementation, turn to this book to get some concrete answers.  Not all of the advice is solid gold, mind you... but I think it's an excellent resource for a team that is new to Scrum.

Houston ALT.NET Geek Dinner #2 - Tonight

I'm so lame.  I forgot to post this last week.  If you don't want to be lame like me, then you should head out to Star Pizza tonight at 6pm for some fun-loving tech discussions about ALT.NET and the Houston community.  There will be pizza.  There will be beer.  And there will be a fat guy in a jump suit giving away iPhone 3G's*.

Potential Topics:



Star Pizza
2111 Norfolk
Houston, TX
Date: (Tonight!) July 14th, 2008
Time: 6pm

View Larger Map


* No iPhones will be given away.  And there won't be a fat guy in a jump suit.  I hope.


Friday, July 04, 2008

Prototype Protips #2 - Preventing Memory Leaks & Creating Objects

It’s been almost a year since I posted Prototype Protips #1 (and since then I have grown quite fond of jQuery), but prototype remains an excellent, choice for elegant javascript development still today.

If you aren’t taking advantage of an advanced javascript library, then you are not only missing out on cleaner code, but you are quite possibly likely introducing memory leaks through unfreed event handlers!  IE6 is notoriously bad at this, and you have to take extra caution in EVERY event handler to make sure you aren’t leaking memory.

Douglas Crockford, describes memory leaks in IE like this:

When a DOM object contains a reference to a JavaScript object (such an event handling function), and when that JavaScript object contains a reference to that DOM object, then a cyclic structure is formed. This is not in itself a problem. At such time as there are no other references to the DOM object and the event handler, then the garbage collector (an automatic memory resource manager) will reclaim them both, allowing their space to be reallocated. The JavaScript garbage collector understands about cycles and is not confused by them. Unfortunately, IE's DOM is not managed by JScript. It has its own memory manager that does not understand about cycles and so gets very confused. As a result, when cycles occur, memory reclamation does not occur. The memory that is not reclaimed is said to have leaked. Over time, this can result in memory starvation.

It takes a seriously smart person to understand closures in javascript and why they cause memory leaks.  Personally I’m glad someone else has figured it out!  But it doesn’t mean we can’t protect ourselves and just do things the safe way.

That’s why using a good javascript library is such a no-brainer.  In prototype, you wire up events like this:

Event.observe(window, 'load', function() {
    //do something when the DOM is loaded
});

This is a safe way of adding a load event to the page without erasing any previous load events that were wired up.  (Anyone ever had ASP.NET erase one of your button click handlers?  I have…)

$() extended elements also get this ability.  Say you have a button on the page like this:

<input type="button" id="activate" value="Activate the Reactor!" />

You can wire up a click even to this button by simply…

$('activate').observe('click', function() {
   alert('RUN!');
});

Wiring up events in this manner will help keep you from leaking memory and generally provides better syntax.

Objects

Sometimes when you are writing code that has complex javascript, the functions seem to relate, however they are just placed in file in global scope.  This breaks down when you have lots of scripts from various components on a page and sooner or later things will start to conflict.  I mean, how many init() methods can you have on a page?  Would you write a C# program with nothing but 1 class?

Grouping this functionality into javascript classes can really help streamline and encapsulate your javascript code.  This is incredibly useful when you have a script for a control on a page but you need to support having multiple instances of that control on the same page.  On top of that, with ASP.NET you have to deal with the id munging, so things become even harder.

In prototype (as of 1.6), you create a class like this:

var Person = Class.create({
    initialize: function(name, ) {
        this.name = name;
    },

    greet: function() {
        alert("Hello, " + this.name + "!");
    }
});

//create a person instance like this:
var joe = new Person("Joe");
joe.greet();   // -->  "Hello, Joe!"

The initialize() method is like a constructor.  It will get called when you “new” up your javascript class.  You can extend this object and add more functionality to it pretty easily.

Using Javascript Classes for your Components

Let’s say you want to create a type-ahead auto-complete textbox using ASP.NET.  Ignoring some of the canned-solutions for the sake of discussion, let’s also say we have to write it from scratch.  We’ll need some script to monitor the value of the textbox, and to send an ajax request with the current value, and to handle the response & fill in a list of values.

Here’s an example of how (a very crude) implementation might look:

 

//referenced (or rendered) only once
var TypeAheadTextbox = Class.create({
    initialize: function(textbox_id, indicator_id, results_id) {
        this.textbox = $(textbox_id);
        this.indicator = $(indicator_id);
        this.results = $(results_id);        
        
        //use .bindAsEventListener to preserve the 'this' pointer
        this.textbox.observe('keyup', this.processKey.bindAsEventListener(this));        
    },
    
    processKey: function() {
        //ignoring the interval, we'll just fire an ajax request for every letter
        var value = $F(this.textbox);
        this.indicator.show();
        new Ajax.Request('/pirate_ships/find', {
            method: 'get',
            params: 'filter=' + value,
            onComplete: function() { this.indicator.hide(); },
            onFailure: function(e) { alert("Error with ajax request: " + e); },
            onSuccess: function(response) {
                this.results.innerHTML = response.responseText;                
            }
        })
    }    
});


//initializing multiple controls
Event.observe(window, 'load', function(){
    var textbox1_autocomplete = new TypeAheadTextbox(
        '<%= txtbox1.ClientID %>', 
        '<%= indicator1.ClientID %>',
        '<%= results1.ClientID %>'
    );
    
    var textbox2_autocomplete = new TypeAheadTextbox(
        '<%= txtbox2.ClientID %>', 
        '<%= indicator2.ClientID %>',
        '<%= results2.ClientID %>'
    );
});

Of course this code is incomplete, but it’s definitely a start, and it amazes me at how simple it is.  One hiccup that I ran across doing something similar was the .bindAsEventListener noise.  You can read about that here, but the short of it is that javascript will lose ‘this’ in those types of event handlers.  Using .bindAsEventListener will fix this for you.Another trick is to use something like:

initialize: function(msg) {
    var self = this; //for use later
    this.msg = msg;
},

foo: function() {
    alert(self.msg);
}

We’ve also isolated the area where we need to pass in the generated ClientID of the controls we want to interact with.  This snippet of code can reside on the page, whereas the rest of the code reside ina separate file.

I’m no javascript ninja, but I have a vast appreciation for all that these libraries give us.  Yet it still amazes me that a lot of people don’t use them!  So go tell a friend or co-worker about the benefits of a good javascript libary.

Wednesday, July 02, 2008

Precompiling ASP.NET Applications And The Dreaded LoadResource Failure Error

This is a very strange problem that burned quite a bit of my time last week.  Hopefully this will be found by someone else with a similar dilemma.

Basically I’ve got a central web application project that I use to house user controls to be shared by many applications.  To accomplish this, I’ve followed this post by K. Scott Allen (of OdeToCode fame).

Since precompiling an ASP.NET project produces (at least) one funkily-named dll, I decided to ILMerge it with the code-behind assembly.  So the process looks like this:

After-build:

  • Precompile the website, updatable=false, into a temporary directory
  • Merge App_Web*.dll & my project assembly together into one
  • Reference this in another project & use the controls

If you look at this dll in reflector, you get something like this:

Reflector1

As a side-note, If you haven’t ever tried doing this before then you should know that you have to reference the compiled user control classes from the ASP namespace in your pages… so in this example <cc1:keywordgrid_ascx /> rather than <cc1:KeywordGrid />.

This first control worked just fine.  It was basically a custom GridView control.  Then I added that next control, SelectKeywords.  Adding it caused the assembly to crash upon loading, i.e. even when we weren't using SelectKeywords it was crashing pages that used KeywordGrid.  The error:

Error1

This happened in the constructor of any control in the assembly.  Digging a little deeper into the error, I found that this happens when your user control has literal content in it that gets embedded as a Win32 resource file during precompilation.

In reflector, this is what it looked like:

Reflector2

When building the control tree, the compiler was embedding literal content as a resource.  To try and fix the issue, I started removing literal content.  Bit-by-bit I removed some HTML until the error went away.  This is what reflector looked like when it was working:

Reflector3

But where is this resource file?  Clearly the assembly is referencing some resource file somewhere, right?  I peeked into Temporary ASP.NET Files to find the answer.

Temp-aspnet-files

If you open this up in Notepad, you’ll find your missing literals!

Notepad-resource

For some reason, this file wasn’t making it into the final assembly. 

So if you have more than 256 characters of contiguous literal content, the compiler will put this into a resource file (likely for perf reasons).  Sadly there is no way to turn this off. 

I’m not sure if ILMerging the assembly was the reason why this was getting lost, but luckily David Ebbo from Microsoft was able to provide me with a couple of options.

  1. You can break up the literal content by injecting empty code blocks like <% %> but who would really want to do that?
  2. You can fake out the compiler & supply a code provider that claims to not support Win32 Resource files.  This is what I chose.

I created a class (of course in another assembly, otherwise you have a chicken-and-egg problem):

public class CustomCodeDomProviderWithoutWin32ResSupport : CSharpCodeProvider
{
    public override bool Supports(GeneratorSupport generatorSupport)
    {
        if ((generatorSupport & GeneratorSupport.Win32Resources) == generatorSupport)
           return false; 

        return base.Supports(generatorSupport);
    }
}

To get the precompiler to use this new class, add this to your web.config:

<system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="MyAssembly.CustomCodeDomProviderWithoutWin32ResSupport, MyAssembly" warningLevel="1" />
    </compilers>
</system.codedom>

Now my user controls can have as much literal content as they need and resources will not get added.  I’d like to thank David Ebbo for his assistance in helping me figure this out.  Thanks David!

Personal Loan - New York Hotel - Savings Accounts - Phoenix Pools