Saturday, June 6, 2009

Is Google Wave Doomed to Fail? Not So Fast...

Last weekend, I spent a good portion of my "free-time" watching the one-hour-twenty-minutes Google Wave demo from the Google I/O conference.

While I found my self getting caught up in the excitment of it all, I couldn't help but feel a sense of DejaVu...as if I had seen something like this before, that had failed miserably. And then, I checked my bookshelf:

Dreaming in Code by Scott Rosenberg (of Salon.com fame). This book was the real-life chronicle of Mitch Kapor's attempt to create an Open-Source, cross-platform, Personal Information Management (PIM) tool.

For those who have not read the book, it is a frustrating and predictable (in 20/20 hindsight) failure to redefine email, tasks, notes, calendars, etc. as generic items that a user could work with interchangeably. They understood even then that people worked with email and those other "Outlook/Exchange managed items" differently than was originally intended. That the email often turned into tasks, documents, appointments, and near real-time conversations, and historical records. And, it was all Open Source. Thus, my comparison with Google Wave.

The Demise of Chandler

The system was called Chandler and the project had many issues. From a management perspective, the project suffered from:

  • Ill-defined (or undefined) scope and features
  • The Architect in the ivory tower
  • Lack of consistent direction to even incremental delivery
  • Going too deep into niche parts of the solution without starting to solve the core issues
  • Eventually picking regular delivery dates but not delivering any real value that could start to replace Outlook/Exchange
  • Trying to run OSAF so differently from other Silicon Valley startups that things ran slowly and without consistent direction

From a technological perspective, the solution was problematic because:
  • In an effort to avoid being Microsoft Exchange (and paying consultants ridiculous amounts of money every month to come in and do care-and-feeding), it was decreed that Chandler would be a peer-to-peer system with no server.
  • Web access was an afterthought, and then they had to figure out how to provide web access to a non-centralized peer-to-peer system
  • To enable the client (and really, the WHOLE system) to be cross-platform, they chose to use Python and a Python GUI toolkit (but most people do not have Python on their machines, the GUI toolkit did not fit their needs--especially considering that the UX was not designed until after they had already chosen that toolkit). As a side note, the GUI toolkit was also Open Source and they often had to decide whether to wait for that team to fix issues or to make the changes themselves, which often delayed the fixing of bugs.

As the book finishes, all Chandler ever became was a poor Calendaring system. Disappointing.

What, If Anything, Does Chandler's Failure Predict for Google Wave?

As I watched the demo, I was acutely aware of the overarching goals shared between Chandler and Google Wave, so I started looking for the signs of failure that were so obvious with Chandler...and I saw none of them. In fact, I saw many reasons to be encouraged that Google Wave was not going down the same paths and has a great chance of success and adoption!

  • Even though the project is Open Source, it has been incubating for a year and a half and has been actively used by the team as they go
  • It is a platform and protocol so others can contribute to, build on top of it (ala Google Maps) or adapt it to a non-web-browser environment if they needed
  • Much like AdSense, Wave has the ability to be embedded into and published out to any web page
  • Having proved the basic concept of a Conversation with GMail and the insight of how much time is wasted reading "So-and-so is typing..." is quite unique
  • It can run in the cloud but is also federated and can communicate to peer servers, just like email--which has been EXTREMELY successful
  • Real-time language translation
  • They have already solved many of the challenging issues (real-time collaboration across servers, languages, and business boundaries)
  • The browser is the cross-platform programming toolkit and runtime (so no additional installation required and accessible from anywhere!)
  • Even though they only showed the collaborative creation of a document, Google already has Email, Docs, Spreadsheets, and Calendar, and I'm sure they have the ability to figure out how to tie Calendaring and Office Documents. If they succeed at that...look out Microsoft!
  • This is the team that provided Google Maps, that enabled the coolest Mash-ups, and that clearly recognize that they don't even quite know what Google Wave is going to become. But they recognize that, have solved some very hard problems, and are open with it so we can participate in the conversation about what it should be.

I definitely feel the chances of spectacular failure similar to Chandler are nil. It is still early and it will be facinating to see what tie-ins to existing email and calendaring systems will be during some type of transition time. It will be bizarre to see which companies develop their own servers based on the specs and protocols. It will be awesome to see Waves embedded in blogs and news sites and that we can continue to participate in community and conversation anywhere we happen to be on the web or on our mobile devices.

Sunday, May 3, 2009

Solving Silverlight's Annoying Image Control Error Code: 4001 Category: ImageError Message: AG_E_NETWORK_ERROR

If you've done anything with Silverlight's Image Control, you've certainly seen an error similar to this at some point:

The dreaded AG_E_NETWORK_ERROR from Silverlight's Image control (a.k.a. Error Code 4001, ImageError).

Basically, it means the image file you referenced in the Source attribute has some type of invalid Uri. The typical solution that most blog entries and forum posts offer is simply to fix your Uri reference. For more information on typical scenarios for setting the Image control's Source property, see Pete Brown's post on Path and File Resolution (it is still relevant even though it referenced Silverlight 2 Beta 1).

Who Wrote This Control, Anyway?

Sure, simply fixing the Uri may work for some, but what if you are accessing 3rd party feeds that are passing you invalid Url's or those images simply no longer exist? What if your application just can't gurantee that all the images you try to reference actually exist? It seems pretty poor user experience to popup a scripting error dialog--for each image that is missing! On top of that, the rest of the Silverlight page seems to stop processing!

The further I dug when trying to see what options there were for eliminating this problem, the worse it got:

  • There are no events on the Image control that get thrown when the download error occurs every time; you would think that not being able to find the image referenced would call the ImageError event, but when working with a data source of more than several images, it does NOT trap ALL of the errors! That exception seems to be only work reliably for problems creating a Bitmap from the ImageSource that is a valid, existing Uri...
  • You still see these errors when you are debugging, even though the Application_UnhandledException event handler explicitely checks and is only supposed to report the error to the DOM when a dubugger is not attached...
  • In fact, there is no place in the stack for you to handle these exceptions prior to it being thrown out to the JavaScript function onSilverlightException (it seems to come from the Bitmap class's downloading function and directly calls out to the hosting object tag's "onerror" param)--really!?!?!?
  • The Image control is a sealed class! I always get so frustrated when classes are sealed. Most of the time, it just prevents you from being able to tweak things in a way that works for you. Why do that, really? Given that the Image control can call cross-domain to get images without cross-domain security restrictions, I actually see a possible, valid reason...

The first bullet just ticks me off...really? No reliable events? ImageFailed seemed to work at first, but after a tiny bit of testing, it certainly was not good enough. The second one actually got me quite curious. Looking at the Application_UnhandledException code, we shouldn't be seeing any unhandled Silverlight Exceptions being thrown when a debugger is attached:

 public partial class App : Application
{

 public App()
 {
  this.Startup += this.Application_Startup;
  this.Exit += this.Application_Exit;
  this.UnhandledException += this.Application_UnhandledException;

  InitializeComponent();
 }

 private void Application_Startup(object sender, StartupEventArgs e)
 {
  this.RootVisual = new Page();
 }

 private void Application_Exit(object sender, EventArgs e)
 {

 }
 private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
 {
  // If the app is running outside of the debugger then report the exception using
  // the browser's exception mechanism. On IE this will display it a yellow alert
  // icon in the status bar and Firefox will display a script error.
  if (!System.Diagnostics.Debugger.IsAttached)
  {

   // NOTE: This will allow the application to continue running after an exception has been thrown
   // but not handled.
   // For production applications this error handling should be replaced with something that will
   // report the error to the website and stop the application.
   e.Handled = true;
   Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
  }
 }
 private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e)
 {
  try
  {
   string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace;
   errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n");

   System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight 2 Application " + errorMsg + "\");");
  }
  catch (Exception)
  {
  }
 }
}
 

That made me realize that the Image (or Bitmap) control was somehow handling the exception and explicitly calling the onerror function that is set on the Silverlight control's param list (or via a similar property on the Silverlight ASP.NET control).

Ugh. So the Image control throws an uncatchable exception directly out to JavaScript, does not publish any events you can subscribe to that can help you gracefully handle this very typical situation, and the class is sealed so there is no way for you to override this horrible user experience in a user experience-driven technology! WTF? Who wrote this control, anyway? Maybe I shouldn't know, considering how this all makes me feel ;-).

First, Get The JavaScript Errors to Stop

In the spirit of having a minimal shippable solution, we simply need to find a way to get the JavaScript errors to stop so that our users don't see them when they happen. This isn't necessarily the intended final solution, but will get something working that is acceptable.

I happen to use the object tag that the Silverlight test page gives you, rather than the ASP.NET Silverlight control. There are several reaons for this, including the fact that there is a problem with ViewState and the "ToolboxBitmapImage" for that control that will cause your Silverlight application to stop working until you reset IIS. It was introduced in ASP.NET 3.5 SP1 and Microsoft does not have a solution as of this posting. But I digress...

This first solution is the most basic that works in all circumstances, but it is a bit of a heavy-handed one: simply swallow ALL JavaScript errors. You can do this by simply adding this bit of JavaScript anywhere on your hosting page in your web project:


    

The First Small Refinement - May Be Harmful if Swallowed

You can certainly ship your Silverlight application at this point, so that is great. If you are working in Scrum or some other agile environment, now is a good time to check in to Continuous Integration to have a "worst-case scenario" point that contains potentially shippable software!

Now that you have something you can ship, you can (and should) spend the time to refine and refactor. "Why?" you ask? There are side-effects...

While this solution stops the annoying popups from the Image control, it also swallows JavaScript errors on that page! So certainly, this isn't the best solution, especially during development since you won't see any of your JavaScript errors or any unhandled Silverlight errors at all.

As a result, I recommend showing the errors during development, maybe even in your continuous integration environment, but suppressing them once in a pre-production or production environment. This can be accomplished using either a web.config setting that is read by your code-behind to decide whether your page should add this script block or not, or through conditional attributes on methods that decide whether to swallow the errors or not.

To accomplish the web.config setting per-environment, see my the reference to Scott Hanselman's article in my earlier post on Environment-Specific ServiceReferences.ClientConfig files. Simply create a control that outputs the JavaScript to define the window.onerror function (a Literal control that has its Visible property set based on a web.config setting would do fine. The literal itself would look something like this on the page markup, somewhere inside the head of the page:


    

If you'd like to keep that logic to the page without the overhead of a web.config, you can also use Conditionals. Again, you can create different configurations per environment, or simply differentiate between Debug and Release. First, set a compilation constant in the project Properties of the web project:

In the above example, I used SHOW_SILVERLIGHT_ERRORS as the compilation constant (although, strictly speaking, it's really a flag dealing with all JavaScript errors, not just Silverlight; more on that later). When set, we hide the Literal that outputs the script that swallows the errors. I specifically set Visible to true on the control in the markup so that we swallow the errors unless otherwise specified (would be bad to accidentally go to production without swallowing the errors).

Then, I updated the code-behind of the page to add a conditional onto a method so that the Literal control's visibility is set to false only when we explicitely say we want to show these errors. The logic may seem a little backwards at first, but the goal is to default to swallowing the errors and to only show the errors when we explicitely say that we want to see them:

using System;
using System.Diagnostics;
 
namespace ImageControlFun.Web
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            //Show JavaScript errors (if we are supposed to)
            ShowJavaScriptErrors();
        }
 
        [Conditional("SHOW_SILVERLIGHT_ERRORS")]
        private void ShowJavaScriptErrors()
        {
            SwallowJSErrors.Visible = false;
        }
    }
}
    

With the code above, the ShowJavaScriptErrors method call is only inserted into the MSIL when the compilation constant is defined. Otherwise, there is no function call to turn off the visibility of the Literal.

Good Enough For Now

With either change described above, this is again a good place to check-in your code as a release candidate. You can now control the swallowing of all JavaScript errors depending on the environment using Project Configurations or configuration settings. This definitely can be harmful as it also hides all JavaScript errors on your host page, so we definitely want to continue iterating on this problem. But for now, this will allow you to ship something that can add value.

Some other options for refinement of swallowing JavaScript errors in the future include:

  • Instead of throwing the JavaScript error at the end of onSilverlightException, simply do nothing
  • Instead of throwing the JavaScript error, call an HTTP Handler or Web Service that is hosted in the same site (or on a site that you have a cross-domain exception with) to log all JavaScript errors; great for getting real metrics about what scripting issues users are seeing (Silverlight induced or otherwise)!
  • If you like some combination of options, you could change the onerror param on the object tag (using web.config or conditional compilation) to call the default onSilverlightException locally and in your integration environment and call a different function that does logging in testing, pre-production and production.

Now That the Errors Are Gone...

...you are still left with an experience that leaves something to be desired. At least the user experience is not in-yo-face horrible anymore, but this is Silverlight. It's supposed to be clean, smooth, and high-quality. You get no image when there is an error loading from an invalid source url. There is no "loading" or "default" image that is shown prior to the real image being loaded over the network or in an error condition. Even worse, there doesn't appear to be any events, hooks, or override points to be able to respond to these things! I'm sure these issues all came as ways to ensure there weren't any security holes, but sheesh!

Stay tuned for more iterative refinements on the user experience of the Image control (Behaviors and more)! For now, we've got something working.

In the meantime, I'd love to hear any other solutions folks have come up with!

Sunday, April 5, 2009

Environment-Specific ServiceReferences.ClientConfig for Silverlight

Pretty much every project I work on requires the ability to have environment-specific configuration settings: local, development, testing, and production. It's the last thing I ever want to think about when I'm just trying to get my first iteration of a new project working--and I always regret not building it in from the beginning (especially when it comes time to put the project into Continuous Integration).

Things are no different working with Silverlight. If your Silverlight application makes any service calls, there is a good chance you need to call different Uri's in each environment. The configuration for these services is stored in a file called ServiceReferences.ClientConfig.

Additionally, the web site that hosts your Silverlight application may have the same type of environment-specific requirements. Wouldn't it be nice to have one way of solving this problem?

There are plenty of techniques for accomplishing this type of task, especially for app.config in WinForms and web.config in ASP.NET projects. The usual suspects are:

  • Create a NAnt/MSBuild script that replaces the config file at build-time with the proper one for the environment being built
  • Create an MSI that contains all versions of the config file and deploys the one for the environment from a user-selected chosen by the user (or via command-line in quiet mode
  • Create multiple Solution Configurations for each environment, and replace the config at build-time using a pre-build command.

I've used all three techniques over the years. It turns out that the first two don't make much sense for Silverlight, as the ServiceReferences.ClientConfig is bundled into the XAP file that is created at build time. Sure, you can create some type of post-build step that replaces the ClientConfig file in the XAP, but that could get interesting to manage. You don't have an MSI for a XAP, so that option is out as well.

I heavily favor option 3--especially for Silverlight development. The best description I have seen for accomplishing option 3 is good ol' Scott Hanselman's blog entry on Managing Multiple Configuration Environments with Pre-Build Events. Right at the beginning, he says:

"Here's the general idea. It's not too hard. I'll use an ASP.NET Web Site and web.config as an example, but this will work with most any kind of project, exe's or .dll's."

He's right. It works great for Silverlight projects as well! No ripping apart/adding to XAP files after they're built. No NAnt or MSBuild script required. No manual changing of urls when it comes time to deploy. Just follow his post with the following differences:

  1. Make environment-specific ServiceReferences.ClientConfig files (instead of web.config).
  2. Use a slightly different Pre-Build Event that updates ServiceReferences.ClientConfig instead of web.config
  3. Optionally, update the copyifnewer.bat file to use xcopy /R /Y if you use TFS as your source control (thank you, TFS, for making files read-only...argh.)

I will only describe the differences below.

Make the environment-specific ClientConfig files

Just copy the ServiceReference.ClientConfig file that is built for you:


  
    
      
        
          
        
      
    
    
      
    
  

    

and paste the original file into configuration-specific files, such as ServiceReferences.ClientConfig.dev, ServiceReferences.ClientConfig.testing, and ServicesReferences.ClientConfig.production (and don't forget the .debug and .release versions to cover your default solution configurations).

Here's an example of ServiceReferences.ClientConfig.Testing (notice how the highlighted areas are different between the two files; this is the typical place where all of the ClientConfigs differ).


  
    
      
        
          
        
      
    
    
      
    
  

    

Create the Pre-Build Event to Copy the ClientConfig

Instead of replacing the web.config file, change the Pre-Build Event to replace the ServiceReferences.ClientConfig (only when it is different from what's already there):

"$(ProjectDir)copyifnewer.bat" "$(ProjectDir)ServiceReferences.ClientConfig.$(ConfigurationName)" "$(ProjectDir)ServiceReferences.ClientConfig"
        

Add the copyifnewer.bat File

And don't forget to add the copyifnewer.bat file to the root of your project (see Scott's post, lined above) for details on that. Remember, if you use TFS (or some other source control that insists on marking checked in files as Read-Only, you will need to update the .bat file to overwrite without prompting even if the file is Read-Only (xcopy /R /Y worked like a champ).

The Result

Here's a shot of the project structure, once these files are all added:

I love this solution as it works locally, within the IDE as well as on a build server using MSBuild, NAnt calling MSBuild, or NAnt calling devenv.exe directly!