Monday, July 27, 2009

Why WPF databinding is an awful technology

[Edited 10/23/09: I wrote this blog post when I was in a pissy mood, because I'd just wasted a day trying to get some WPF databindings to work. I still agree with my critiques of WPF/Silverlight databinding, and I think that MS really needs to rethink some aspects of their databinding implementation, especially their failure to implement a strongly typed DataContext. Still, if I were to write this post now, I'd be less inflammatory, especially in my opening paragraphs. Just to put it in perspective. But on to the original post . . . ]

I hate WPF databindings. Let me say that again. I hate WPF databindings. Of all the technologies to come out of Microsoft over the last five years, WPF (and Silverlight) databindings are easily the most awkward to configure, the least intuitive to use, the hardest to troubleshoot, and the easiest to get wrong. XAML itself is an ugly, verbose language, but once you get used to it, it's at least possible to get the UI pieces right with a little bit of troubleshooting. But setting up bindings in XAML is a different world altogether.

Microsoft claims, with some justice, that XAML/WPF/Silverlight provide for a very clean separation of concerns between the UI and the business logic, and this is true. But the reason to have a clean separation of concerns is to make things easier for the developer and the designer. And any benefits that may accrue from this theoretical achievement are entirely wiped out by Microsoft's piss-poor, half-assed, god-awful implementation. I've spent many painful hours troubleshooting WCF bindings, I've pored over bizarre Linq-to-Entity query side-effects until I couldn't see straight. But my time has never been so fruitlessly spent as trying to get my databindings right in XAML.

I could give you an example from nearly every single WPF form I've ever tried to design, but let's just go with this one, taken straight from this MSDN article. Create a simple form that looks like this:

<Window x:Class="WpfDatabindingDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <XmlDataProvider x:Key="MoreColors" XPath="/colors">
            <x:XData>
                <colors >
                    <color name="pink"/>
                    <color name="white"/>
                    <color name="black"/>
                    <color name="cyan"/>
                    <color name="gray"/>
                    <color name="magenta"/>
                </colors>
            </x:XData>
        </XmlDataProvider>
    </Window.Resources>
    <Grid>
        <ListBox x:Name="lbColor" Width="248" Height="200"
            IsSynchronizedWithCurrentItem="True"
            ItemsSource="{Binding Source={StaticResource MoreColors},
            XPath=color/@name}">
        </ListBox>
    </Grid>
</
Window>


And note that in the Visual Studio form designer, you see the data, as you'd expect:



But then try to run the application, and you'll note that the data doesn't show up.



There's no explanation as to why the data doesn't show up. There's no error in the Visual Studio debug output window. There's no error anywhere. The data simply isn't there. And note that this WPF binding error is taken straight from Microsoft's own documentation! But the biggest problem isn't that Microsoft's own proof-of-concept, easy-as-pie, this-is-how-you-learn-this sample code doesn't work: it's that your program fails silently, without an error, and without any debugging information.

If you want to know why it doesn't work, you have to add a short but ugly little piece of XAML to turn on extended diagnostics. (By "ugly" I mean, "You'll never remember what to type unless either (a) you Google it and then cut-and-paste the example into your code; or (b) this is the 57th time you've had to do this." I'm quickly approaching (b).)

First you have to register the diagnostics namespace:

<Window x:Class="WpfDatabindingDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="Window1" Height="300" Width="300">

And then you have to turn the trace level on to "High".

<ListBox x:Name="lbColor" Width="248" Height="200"
    IsSynchronizedWithCurrentItem="True"
    ItemsSource="{Binding Source={StaticResource MoreColors},
    XPath=color/@name,
    diagnostics:PresentationTraceSources.TraceLevel=High}">

And then when you run your app, you finally see this error in the Visual Studio output window:

System.Windows.Data Error: 47 : XmlDataProvider has inline XML that does not explicitly set its XmlNamespace (xmlns="").

Another hour or so of poking around on Google, and you realize that you have to add the blank namespace to your XML data, like so:

<XmlDataProvider x:Key="MoreColors" XPath="/colors">
    <x:XData>
        <colors xmlns="" >
            <color name="pink"/>
            <color name="white"/>
            <color name="black"/>
            <color name="cyan"/>
            <color name="gray"/>
            <color name="magenta"/>
        </colors>
    </x:XData>
</XmlDataProvider>

And after only two or three hours of troubleshooting (if you're lucky), you've fixed your problem. But my point is that this isn't something that you should have had to troubleshoot. This is something that either (a) should have worked out of the box, as the MS sample code led you to believe, or (b) should have thrown a simple and explanatory runtime error, or (c) even better, should have been caught at compile-time, since everything that the compiler needed to know to realize that this wouldn't work is already there in the source code. Any of those three options would have been fine. But it's simply not acceptable to fail silently, without even a hint about what's going wrong.

This is just one example, of course.  I could produce plenty of others, but then this post would get even more boring and repetitive than it is.

The fundamental problem with Microsoft's data binding implementation is that it's a significant step backwards in terms of computer language theory. Folks have known for years that strongly typed languages help you write better code. It's better to catch an error when you're writing your code than to catch it at compile-time;  it's better to catch an error when you compile your code than to catch it at run-time; if the compile doesn't catch it, at least your automated tests should be able to; and of course it's better to throw an error (even at runtime) than to fail silently. But the way that MS has implemented bindings, there's no way to catch errors at any of these steps: neither when you're designing your form, nor when you're compiling your app, nor when you're running your tests, and most inexplicably, not even when you're running your application. The minute you type "{Binding…}", you're working without a net.

For instance, MS could have implemented the XmlDataProvider in such a way as to allow (or even require) you to specify an XSD file as a parameter.  If they had, not only could you get Intellisense when typing in your bindings, but you could also receive a helpful compiler error if it sees, say, that you've bound your ListBox ItemsSource to an element or an attribute that doesn't exist. Similarly, if you're using an object data source, you should be allowed to declare the type up-front, and then only bind to properties available in that type. And if the binding fails at run-time, e.g., the object isn't available, or doesn't have the properties available that you're trying to access, you should get exactly the same sort of error that you'd get in C# if you tried to access a non-existent property on a class from a third-party DLL. In other words, it should not only fail (which it does now), but it should tell you why it's failing, and it should tell you this before you ever run your application.

In other words, MS should have made the DataContext property strongly typed. It would allow the Visual Studio tools to do in milliseconds what you can only do now through hours of futile Googling and days of pointless troubleshooting. Microsoft decided that they wanted the extra flexibility that comes from not having any type safety, and I agree that there are scenarios where this flexibility is helpful. But there's a reason nobody writes enterprise applications in VBScript: type safety is orders of magnitude more important than flexibility when working with complex systems. And I would willingly, in a heartbeat, give up some extra flexibility, in exchange for knowing immediately why my bindings won't work, instead of waiting until I run the app to find out that they don't work and not having a clue as to why.

Undoubtedly this would require a pretty significant rethinking of how data binding works with XAML. Neither WPF nor Silverlight would look the same when they were done. But that may be exactly my point. MS had the chance to get WPF right; and they missed a huge opportunity. And until they fix this, their databinding implementation will remain a significant drag on the adoption of WPF and Silverlight. Without strong typing, WPF databinding is just too difficult to use.

No comments: