Saturday, June 6, 2009

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.

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:

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:

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

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.

"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:

Use a slightly different Pre-Build Event that updates ServiceReferences.ClientConfig
instead of web.config

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):

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!