Showing posts with label Silverlight Toolkit. Show all posts
Showing posts with label Silverlight Toolkit. Show all posts

Wednesday, April 13, 2011

Impressions of MIX 2011

Bing sent me to MIX as an attendee this year and so far it has been a blast.  It’s great to be able to listen to customer concerns and take the temperature of a technology by checking out how many people show up to a talk.  Most of all it’s fun to talk to the people who own the technologies I care about (especially the developers).  It’s also nice to occasionally get props for my contributions to Silverlight even though I’ve since left the team. Smile

The feedback from customers continues to be concern about the future of Silverlight and all I can do is reference the blog post “Standards-based web, plugins, and Silverlight.”  It also appears that many developers are somewhat wary of exploring HTML5.  It’s my feeling they might be more enthusiastic if the toolset were comparable to what Microsoft currently offers to Silverlight developers.  In a perfect world the release of IE9 would be accompanied by a Javascript unit testing framework built into Visual Studio, comprehensive Blend support for HTML5, SVG, and jQuery templates, a Visual Studio update providing ES5 intellisense support, a jQuery plug-in for data-binding, and support for writing server-side code using the IE9 Javascript engine.  Instead web developers got yet another templating engine for ASP.NET MVC (Razor) which - although very nice – doesn’t really solve any new problems.

One of the key reasons our developers are so loyal is that we provide them with such excellent tooling for our platforms.  IE9 has some great debugging and diagnostic tools but that’s not enough.  Developers need development tools and libraries that will fill the gaps they’ll encounter when they leave the warm embrace of the CLR and enter the realm of the browser.  The Javascript version of Reactive Extensions is a good start.

I know the transition from Silverlight to HTML5 can be painful as I made the switch myself when I joined the Bing team.  The good news - and it’s something I think we can do a better job of emphasizing - is that by cobbling together frameworks like jQuery, jQuery templates, and Knockout.js it’s possible to follow a development model similar to Silverlight and consequently achieve similar levels of productivity. 

Looking forward to tomorrow’s talks!

Sunday, April 4, 2010

I’ve left the Silverlight team

I recently left the Silverlight team.  Silverlight is a great team to work for and I have tremendous confidence in the continued success of the product.  However at this point in my career platform development doesn’t appeal to me very much. I’d rather be much higher up in the stack working on user-facing technology.  To that end I’ve joined a research team that’s developing semantic web technology for Bing (among other things).

Unfortunately this will mean that I won’t be able to blog as much about what I’m doing because most of my work will be under NDA.  I can say that it is very exciting though!  If there’s one thing Microsoft does better than anyone it’s funding and productizing research (although there’s still room for improvement).

Working for the Silverlight team has been a real thrill.  It was great to be able to fight for and contribute features to the platform that I cared about. I’m proud to have been a part of Silverlight 4 and I can’t wait to use it on my new team.

Friday, October 23, 2009

Silverlight Toolkit Drag Drop (Part 2): Customizing Drag and Drop Behavior

In part 1 you were introduced to the new Silverlight DragDropTarget controls.  In addition to being powerful these controls are very flexible, allowing developers to customize almost every single aspect of their behavior.  In order to understand how to customize these controls you must first understand how they communicate.  The good news is that DDT’s communicate via a WPF-compatible implementation of the drag/drop events!

A WPF-Compatible (mostly) Implementation of Drag and Drop

The Silverlight Toolkit Drag and Drop Framework is a compatible subset of WPF’s drag and drop methods and events – with a few small exceptions.  The diagram below demonstrates how DDTs are layered on top of this WPF-compat framework.  DDTs use these events and methods to communicate with each other.

dragdropdiagram 

The fact that the Silverlight Toolkit contains a full implementation of WPF’s drag and drop stack gives the developer tremendous flexibility.  They can…

  • Use DDTs.
  • Communicate with and customize DDTs by handling the drag and drop events.
  • Choose not to use DDTs and use the WPF-compat API’s to initiate and manage their own drag operations.
  • Add native drag and drop support to their custom controls by implementing the IAcceptDrop interface.
  • Create new DDTs for existing controls by creating a class that inherits from DragDropTarget.

Differences between Silverlight Toolkit and WPF Drag and Drop API’s

Different Namespaces

The most obvious difference between the Silverlight implementation of drag and drop and that of WPF is that the Silverlight drag and drop objects are located in the Microsoft.Windows namespace rather than in the System.Windows namespace.  When writing drag and drop code it’s good practice to insert the following code in your source file:

#if SILVERLIGHT
using Microsoft.Windows;
using SW = Microsoft.Windows;
#else
using SW = System.Windows;
#endif

Using an alias for the namespace with the drag/drop objects ensures that you can write code that can be easily ported to WPF or a future version of Silverlight with core-level drag/drop support.  All of the code samples in this series of blog posts assume that you’ve defined the alias “SW” as Microsoft.Windows in Silverlight and System.Windows in WPF.

Starting a Drag Operation

Keep in mind that if you intend to use a DDT you don’t need to know how to do this because DDT’s automatically initiate drag operations when an item is dragged.  However if you want to initiate a drag operation yourself you’ll need to know about some subtle (but necessary) differences between the Silverlight’s and WPF’s DoDragDrop methods. 

In WPF the DoDragDrop method blocks until the drag operation completes.  In Silverlight there are no blocking UI methods so it is necessary to listen for a DragDropCompleted event if you want to perform an operation when a drag completes. 

It is still possible to write cross-platform code if you use the following pattern:

// Define an action to be executed when the drag completes.
Action<SW.DragDropEffects> dragDropCompleted =
    effects => 
    {
        Console.WriteLine(string.Format(“Drag finished: {0}”, effects)));
    };
    
#if SILVERLIGHT

// Create a handler for the DragDropCompleted event which invokes the action.
EventHandler<DragDropCompletedEventArgs> dragDropCompletedHandler = null;
dragDropCompletedHandler =
    (sender, args) => 
    {
        // Detach the handler so that this action is only executed once.
        SW.DragDrop.DragDropCompleted -= dragDropCompletedHandler;
        dragDropCompleted(args.Effects);
    };

// Attach the handler to the DragDropCompleted event.
SW.DragDrop.DragDropCompleted += dragDropCompletedHandler;

// Begin the drag operation.
SW.DragDrop.DoDragDrop(
    sourceControl, 
    data, 
    SW.DragDropEffects.All, 
    SW.DragDropKeyStates.LeftMouseButton);    

#else

// If WPF then just begin the drag operation.
SW.DragDropEffects effects = 
    SW.DragDrop.DoDragDrop(
        sourceControl, 
        data, 
        SW.DragDropEffects.All);
        
// Code blocks until drag is finished...
dragDropCompleted(effects);

#endif

Note that in addition to being asynchronous the Silverlight version of DoDragDrop accepts an additional parameter: the initial DragDropKeyStates.  In the example below this is SW.DragDropKeyStates.LeftMouseButton.

SW.DragDrop.DoDragDrop(
    sourceControl, 
    data, 
    SW.DragDropEffects.All, 
    SW.DragDropKeyStates.LeftMouseButton);

In Silverlight (unlike WPF) it is not possible to sample the mouse state.  Therefore the developer must track the state of the mouse keys and pass it to DoDragDrop.  This is straightforward if, as is commonly the case, the drag operation begins with a mouse click. 

button.MouseLeftButtonDown += 
    (sender, args) =>
    {
        SW.DragDrop.DoDragDrop(
            button, 
            "Some text to transfer", 
            SW.DragDropEffects.All,
            SW.DragDropKeyStates.LeftMouseButton);
    };

Although the DragDropKeyStates enumeration is a flags enum which includes members that track keyboard states there is no need to include these.  The DoDragDrop method will sample the keyboard and update the value when invoked.

Listening to Drag Source and Drag Target Events

In WPF there are two ways to attach a handler to an event.  The first is to attach a handler to the event on the UIElement and the second is to attach a handler to the attached event object:

// Works in WPF.
myButton.DragOver += 
    new SW.DragEventHandler((o, a) => Console.Write(“button dragged over.”));

// Works in WPF and Silverlight Toolkit Drag Drop Framework.
myButton.AttachEvent(
    SW.DragDrop.DragOverEvent, 
    new SW.DragEventHandler((o, a) => Console.Write(“button dragged over.”),
    false);

Silverlight Toolkit’s Drag and Drop Framework supports the latter approach for all drag target and source events.  By using AttachEvent and RemoveEvent you can write code that will work on WPF and Silverlight.  For more information on attached events see the Attached Events Overview on MSDN.

Note: DDTs implement the familiar WPF events so you don’t need to use the more verbose attached event approach when you’re working with them.

// DragDropTarget's have the WPF events.
myListBoxDragDropTarget.DragOver += 
    new SW.DragEventHandler(
        (o, a) => Console.Write(“DDTs make everything easier!”));

Indicating that an Element Can Accept a Drop Operation

In WPF all UIElements have an AllowDrop property which determines whether they will receive drag/drop events.  As it isn’t possible to add properties to existing types the Silverlight Tookit implementation of drag and drop uses an AllowDrop attached property on the static DragDrop object:

WPF

<Rectangle AllowDrop="True" />

Silverlight

<Rectangle 
    xmlns:msWindows="clr-namespace:Microsoft.Windows;assembly=System.Windows.Controls.Toolkit" 
    mswindows:DragDrop.AllowDrop="True" />

Customizing DragDropTarget Behavior

DDT’s do their best to determine the allowed effects of a drag operation by examining the bound collection and the items inside of them.  For example if a DDT’s control is bound to a ReadOnlyCollection or an array then the allowed effects of a drag will not include DragDropEffects.Move and the allowed effects of a drop will be DragDropEffects.None.  By default a DDT never sets the allowed effects of a drag to DragDropEffects.Copy because there is no generic way of cloning an item in Silverlight (ICloneable does not exist).  But what if we wanted the effect of a drop to be a Copy operation?

Let’s say we have two DDT’s, one containing a ListBox and the other containing a DataGrid.

listboxanddatagrid

<StackPanel Orientation="Horizontal">
    <controlsToolkit:ListBoxDragDropTarget x:Name="listBoxDragDropTarget"  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
        <ListBox x:Name="listBox" VerticalAlignment="Stretch" SelectionMode="Extended"  Height="300" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding Image}" />
                        <TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="4,0,0,0" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>
    </controlsToolkit:ListBoxDragDropTarget>
    <dataToolkit:DataGridDragDropTarget x:Name="dataGridDragDropTarget" msWindows:DragDrop.AllowDrop="true"  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
        <data:DataGrid x:Name="dataGrid" AutoGenerateColumns="False"  Height="300" >
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Binding="{Binding Name}" Header="Name" />
                <data:DataGridTextColumn Binding="{Binding Reputation}" Header="Reputation"  />
                <data:DataGridTextColumn Binding="{Binding GamerScore}" Header="GamerScore" />
            </data:DataGrid.Columns>
        </data:DataGrid>
    </dataToolkit:DataGridDragDropTarget>
</StackPanel>

Each control is bound to an collection of Gamer instances.

class Gamer {
    public string Name {get; set}
    public double Reputation {get; set}
    public int GamerScore {get;set;}
    
    // Our custom copy method.
    public Gamer Clone()
    {
        return 
            new Gamer 
            {
                Name = this.Name,
                Reputation = this.Reputation,
                this.GamerScore = this.GamerScore
            };
    }
}

// snip...

// No need to use an ObservableCollection.  We don't
// want items removed from the ListBox.
listBox.ItemsSource = 
    new []
    {
        new Gamer 
        { 
           Name = "Mark Rideout",
           Reputation = 82.234,
           GamerScore = 23432
        },
        // etc.
   };

// Using an observable collection ensures that changes
// to the items source collection will be reflected 
// in the grid immediately.
dataGrid.ItemsSource =
    new ObservableCollection<Person>(); 

When the user drags Gamers from the ListBox to the DataGrid we’d like to add a copy of the Gamer object to the DataGrid rather than add a reference to the object in the ListBox to the DataGrid’s bound collection (the default action).  In order to ensure that when an item is dragged from a ListBox the allowed effect is DragDropEffects.Copy rather than DragDropEffects.Link we set the ListBoxDragDropTarget’s AllowedSourceEffects property.

listBoxDragDropTarget.AllowedSourceEffects = SW.DragDropEffects.Copy;

Since the DataGridDragDropTarget doesn’t know how to handle a Copy operation we need to handle its Drop event.

using System.Collections.ObjectModel;

// snip...
dataGridDragDropTarget.Drop += 
    (sender, args) =>
    {
        // Only handle this event if it's a copy.  If the event is not handled
        // the DDTs base implementation of Drop will execute.
        if ((args.AllowedEffects & SW.DragDropEffects.Copy) == SW.DragDropEffects.Copy)
        {
            IList<Person> dataSource = dataGrid.ItemsSource as IList<Person>;
            
            // Retrieve the dropped data in the first available format.
            object data = args.Data.GetData(args.Data.GetFormats()[0]);
            
            // The data is the ItemDragEventArgs that was created by the DDT when
            // the drag started.  It contains a SelectionCollection.
            // SelectionCollection's are used by DDTs because they can transfer 
            // multiple objects.  The fact that they store the indexes of the 
            // objects within the source collection also makes reordering items
            // within a source possible.
            ItemDragEventArgs dragEventArgs = data as ItemDragEventArgs;
            SelectionCollection selectionCollection = dragEventArgs.Data as SelectionCollection;
            if (selectionCollection != null)
            {
                IEnumerable<Gamer> gamers = selectionCollection.Select(selection => selection.Item).OfType<Gamer>();
                if (gamers.Any())
                {
                    foreach(Gamer gamer in gamers)
                    {
                        // Invoke our custom Clone method to make a copy.
                        dataSource.Add(gamer.Clone());
                    }
                    args.Effects = SW.DragDropEffects.Copy;
                    args.Handled = true;                
                }
            }
        }
    };

Now when we drag items from our ListBox to our DataGrid we will get copies of the Gamer objects and the ListBox’s contents will be unchanged.

There are many more extensibility points in DDT’s.  I encourage you to take a close look at the events and properties.  I think you’ll be impressed by how flexible these controls are.

Powerful and Flexible

By now it should be clear that the Silverlight Toolkit Drag and Drop Framework is more than a few DDTs that add drag and drop behavior to a certain Silverlight controls.  It is also a rich set of WPF-compatible events and methods that can be used with any Silverlight control - including your own!

Next time: Adding Drag and Drop support to your custom controls!

Monday, October 19, 2009

New with the Silverlight Toolkit: Drag and Drop Support for all your Favorite Controls! (Part 1)

One of the highest voted requests on the Silverlight Toolkit CodePlex page is drag and drop support for the TreeView control.  The good news is the Toolkit team listened and has not only delivered Drag and Drop for the TreeView, but they’ve added support for all the major Silverlight controls!  In the October Toolkit refresh you’ll find controls which add Drag and Drop support to all of your favorite controls – no code required!

dd1 dd2 dd4  dd4

Introducing The Drag/Drop Target Controls

A DragDropTarget is a Content Control that adds default drag and drop actions to the control nested inside of it.  DragDropTarget’s provide the following functionality:

1.  Initiates a drag operation when an item container is dragged.

2.  Displays a snapshot of the the item container by the mouse while it is being dragged.

3.  Handles drag target events and specifies which drag operations are possible by examining the item source bound to the nested control.

4.  If an item is dropped onto the drag drop target, it is added to the nested control if the nested control is bound to an ObservableCollection (or any collection that implements INotifyCollectionChanged and contains the same type of items as the item that was dropped).

5.  Where possible, scrolls vertically and horizontally when an item is dragged near the edge of the control.

This release of the toolkit introduces the following implementations:

  • ListBoxDragDropTarget
  • TreeViewDragDropTarget
  • DataGridDragDropTarget
  • DataPointSeriesDragDropTarget

ListBoxDragDropTarget

In this example we have a ListBox created in XAML and bound to an observable collection.

<ListBox x:Name=”myListBox />

To add drag and drop functionality to our ListBox we nest it inside of the ListBoxDragDropTarget control…

<toolkit:ListBoxDragDropTarget
  xmlns:toolkit="System.Windows.Controls.Toolkit"
  xmlns:mswindows="Microsoft.Windows"
  mswindows:DragDrop.AllowDrop=”True”>
     <ListBox x:Name="myListBox">
         <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
     </ListBox>
</toolkit:ListBoxDragDropTarget>

…and that’s it!  Now we can drag items to and from our ListBox.

listbox1

If the SelectionMode of the ListBox is set to Multiple or Extended it is also possible to drag and drop several items at once.

listbox2

“Why did you need to specify the ItemPanelTemplate?”

The ListBoxDragDropTarget cannot reorder items in a ListBox when it is virtualized because it cannot conclusively determine the index of each item.  Therefore we replace the default Panel (VirtualizedStackPanel) with a normal StackPanel.  If you don’t need to enable the reordering items within the ListBox then it isn’t necessary to specify the ItemPanelTemplate.

TreeViewDragDropTarget

The TreeViewDragDropTarget is used the same way as the ListBoxDragDropTarget.

<controlsToolkit:TreeViewDragDropTarget msWindows:DragDrop.AllowDrop="true"  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
    <controlsToolkit:TreeViewDragDropTarget.Resources>
        <win:HierarchicalDataTemplate x:Key="hierarchicalTemplate" ItemsSource="{Binding Reports}">
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding Image}" Width="16" Height="16" />
                <TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="4,0,0,0" />
            </StackPanel>
        </win:HierarchicalDataTemplate>
    </controlsToolkit:TreeViewDragDropTarget.Resources>
    <controls:TreeView ItemTemplate="{StaticResource hierarchicalTemplate}" Height="300" >
    </controls:TreeView>
</controlsToolkit:TreeViewDragDropTarget>

Note that there is no need to specify the ItemPanelTemplate because in Silverlight the TreeView is not virtualized.

treeview1 

The TreeViewDragDropTarget provides all the functionality you’ve come to expect from Windows Explorer (and more). 

  • During a drag operation parent’s expand when hovered over after a short delay
  • Dragging a parent into itself is forbidden
  • The ability to drag above, below, and into a node is supported

All of these actions can be overridden or cancelled using the events.

DataGridDragDropTarget

Using the DataGridDragDropTarget is straightforward:

<dataToolkit:DataGridDragDropTarget msWindows:DragDrop.AllowDrop="true"  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
    <data:DataGrid x:Name="dataGrid" AutoGenerateColumns="False"  Height="300" >
        <data:DataGrid.Columns>
            <data:DataGridTextColumn Binding="{Binding Name}" Header="Name" />
            <data:DataGridTextColumn Binding="{Binding Reputation}" Header="Reputation"  />
            <data:DataGridTextColumn Binding="{Binding GamerScore}" Header="GamerScore" />
        </data:DataGrid.Columns>
    </data:DataGrid>
</dataToolkit:DataGridDragDropTarget>

datagrid1

Like the ListBoxDragDropTarget, multiple selection is supported.  However the DataGridDragDropTarget has some limitations due to the fact that it is not an ItemsControl and it is virtualized, specifically that reordering items within a DataGrid is not supported.

DataPointSeriesDragDropTarget

The DataPointSeriesDragDropTarget supports all of the series that ship with Silverlight Charts.  However it is used slightly differently than other DragDropTargets.  Instead of wrapping the individual series it is necessary to put the entire Chart in the DragDropTarget.

<chartingToolkit:DataPointSeriesDragDropTarget msWindows:DragDrop.AllowDrop="true" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
    <chartingToolkit:Chart x:Name="chart" Background="White">
        <chartingToolkit:ColumnSeries IndependentValueBinding="{Binding Name}" DependentValueBinding="{Binding Reputation}" Title="Reputation" />
        <chartingToolkit:PieSeries IndependentValueBinding="{Binding Name}" DependentValueBinding="{Binding GamerScore}" Title="Gamer Score" />
    </chartingToolkit:Chart>
</chartingToolkit:DataPointSeriesDragDropTarget>
chart1

“What if a Chart contains multiple series?”

The DataPointSeriesDragDropTargets allows diambiguation between the series by detecting drop operations on the legend.  In other words, items can be dropped both onto the surface of a series or its legend items.  In the example below the Chart contains two series: a Pie series and a column series.  Items can be dragged from a list box into the column series by dropping the items on the column series’  legend item.

chart2

chart3

It’s also easy to drag items between series, either by dropping data directly onto a series data point or onto their legend item entries.

chart4

A Labour of Love

Silverlight Drag and Drop support is a personal, off-hours project that I’ve been working on sporadically for the last few months.  I thought it would be a productive way of learning Rx, the new reactive programming framework for .NET.  When I approached the Toolkit team and asked if they would ship it they agreed.  Of course, having formerly worked for the Toolkit team creating the Silverlight Chart control, ISM, and the Rating control I had some pretty good contacts ;-).

Next time: Overriding the Default Actions of DragDropTargets

Thursday, July 30, 2009

The Joy of Rx: The Event-based Async Pattern vs. IObservable

In part 1 we talked about how to convert events to IObservables.  However Rx isn't just about querying events, it's also about querying asynchronous operations. 

There are two common asynchronous patterns in the .NET framework: the event-based asynchronous pattern and Begin/EndInvoke.  Currently MSDN recommends using the event-based asynchronous pattern where possible.  This recommendation may or may not change when IObservable is released with .NET 4.0.  I want to make clear that the opinions expressed in this post are my own personal views and are not consistent with official MSDN guidance.

Don't use the event-based asynchronous patternOr if you must use the event-based asynchronous pattern be sure to also provide a Begin/EndInvoke version of the API that uses IAsyncResult.  Why?  Because the event-based asynchronous pattern is deeply flawed.  To understand why let's examine a typical piece of code that uses the event-based asynchronous pattern.

Guid token = Guid.NewGuid(); var webClient = new WebClient(); // We need to refer to the identifier within the body // of the method so we must first initialize it to null. // This means we can't use type inference to avoid // having to clutter our code with this absurdly long // (and entirely unnecessary) delegate type. DownloadStringCompletedEventHandler handler = null; handler = (o, a) => { if (((Guid)a.UserState) != token) return; // unhook from the event so that we don't keep firing // after we've gotten our data asynchronously. webClient.DownloadStringCompleted -= handler; if (a.Error != null) { // Handle exception. This may include throwing but // we may also have to invent some method of manually // propagating it if we don't have the knowledge to // handle it at this point and we are in the middle of several //other asynchronous operations. } if (!a.Cancelled) { Debug.WriteLine(string.Format("The downloaded HTML is {0}.", a.Result)); } } // Notice that the only link between the method and the event that returns // its data is a simple naming convention. There's no way to know for _sure_ // which event will return a method's data. client.DownloadStringCompleted += handler; client.DownloadStringAsync(new Uri("http://www.jeffwilcox.com"), token);

A thoroughly awful piece of code no?  The good news is that by adding an extension event to the WebClient class we can wrap the event-based asynchronous pattern in an IObservable.  Using Rx works around all of the the issues pointed out in the comments above. 

Of course it's rather cumbersome to create a new class that inherits from IObservable whenever we need to return the result of an asynchronous operation.  It also seems a little silly given that IObservable only has one method: Subscribe.  Given this fact it's helpful to create an AnonymousObservable class which accepts an action and invokes it when the Subscribe method is called.

internal class AnonymousObservable<T> : IObservable<T> { private Func<IObserver<T>, IDisposable> subscribeAction; public AnonymousObservable(Func<IObserver<T>, IDisposable> subscribeAction) { this.subscribeAction = subscribeAction; } public IDisposable Subscribe(IObserver<T> observer) { return subscribeAction(observer); } }

Now that we've got our handy, reusable AnonymousObservable class we're ready to create our GetDownloadString extension event.

public static class WebClientExtensions { // No need to put Async in the method title, it's implicit given that // an observable is being returned. public static IObservable<string> GetDownloadString(this WebClient client, Uri address) { // Delay action by nesting it in an observable. // Nothing should ever happen until a client subscribes to the // observable, just as nothing should happen until an // IEnumerable is traversed. return new AnonymousObservable<string>( observer => { // Several downloads may be going on simultaneously. // The token allows us to establish that we're retrieving // the right one. Guid token = Guid.NewGuid(); var stringDownloaded = Observable.FromEvent<DownloadStringCompletedEventArgs>(client, "DownloadStringCompleted") // Confirm its our download using captured state variable .Where(evt => ((Guid)evt.EventArgs.UserState) == token) .Take(1); //implicitly unhooks handler after event is received bool errorOccurred = false; // Subscribe to the IObservable. Under the hood Rx // creates an anonymous observer class that invokes // the three actions passed to the Subscribe method. IDisposable unsubscribe = stringDownloaded.Subscribe( // OnNext action ev => { // Propagate the exception if one is // reported. After this, all observers // will continue to propagate this // exception via the OnError method call // until it is caught. if (ev.EventArgs.Error != null) { errorOccurred = true; observer.OnError(ev.EventArgs.Error); } else if (!ev.EventArgs.Cancelled) { observer.OnNext(ev.EventArgs.Result); } }, // OnError action (propagate exception) ex => observer.OnError(ex), // OnCompleted action () => { // No need to call OnCompleted if // there has been an exception. // It is implicit that there will // be no more data. if (!errorOccurred) { observer.OnCompleted(); } }); client.DownloadStringAsync(address, token); return unsubscribe; }); } }

Granted converting the event-based asynchronous pattern to IObservable requires a little bit of code, but the complexity is pushed onto the library developer.  Look at how easy it is to consume the IObservable API.

var webClient = new WebClient(); webClient .GetDownloadString(new Uri("http://www.jeffwilcox.com")) .Subscribe(html => Debug.WriteLine(html));

No need to fuss with event argument objects.  The IObservable/IObserver objects take on the concerns of cancelling and propagating exceptions.  The developer is given exactly what he or she expects: an asynchronously downloaded string containing the contents of a web page.

Building Complex Queries by Combining Asynchronous Operations

Now that we've got our handy GetDownloadString extension method we can build some pretty complex queries with it.  Let's use it to download the HTML from the first ten results pages of a Bing search at once. 

public static class EnumerableExtensions { // This is a very handy extension method missing from Linq. public static IEnumerable<T> Iterate<T>(T initalValue, Func<T, T> next) { yield return initalValue; while (true) { initalValue = next(initalValue); yield return initalValue; } } } // snip... var client = new WebClient(); var uri = "http://www.bing.com/search?q=Rx+framework&first={0}&FORM=PERE3"; var pageSize = 10; var resultIndices = EnumerableExtensions.Iterate(1, prev => prev + pageSize).Take(10); var pageDownloads = resultIndices .Select( resultIndex => client .GetDownloadString(new Uri(string.Format(uri, resultIndex))) // append the result index to the results so we can sort by it later .Select(html => new { ResultIndex = resultIndex, Html = html })) // Merge all of the observables into one so that we can subscribe to all of // them simultaneously. .Aggregate( (simultaneousDownloads, currentDownload) => Observable.Merge(simultaneousDownloads, currentDownload)); var concatenatedHtml = pageDownloads // Convert to a blocking enumerable. It makes no sense for // IObservable to have a GroupBy or OrderBy method because // both these methods must block until all the data is received. // However all of the downloads will begin simultaneously when // we traverse the IEnumerable .ToEnumerable() // Order by the result index .OrderBy(pageDownload => pageDownload.ResultIndex) // Grab the HTML .Select(pageDownload => pageDownload.Html) // Concatenate the HTML strings .Aggregate((accumulatedHtml, html) => accumulatedHtml + html) // Grab the first (and only) result. .First();

Pretty slick right?  There's only one problem. This code doesn't work :-(.  It will block forever.  The problem is that the event-based asynchronous pattern always returns on the thread it originates from and that thread is blocked by the call to GetEnumerator()

In the past returning on the UI thread was convenient because developers didn't have to worry about cross-thread accesses.  With Rx hopping threads is so easy that cross-thread access isn't much of a concern.

var uiContext = SyncrhonizationContext.Current; AsyncAction.Post(uiContext).Subscribe(() => Debug.Write("Now I'm back on the UI thread!"));

Unfortunately the fact that the event-based async pattern hops the the UI thread makes it much more difficult to compose a set of asynchronous operations together and block on the result.  This is why it's important to always provide a Begin/End Invoke version of your event-based, asynchronous API's.

WebClient doesn't provide a Begin/End overload for DownloadStringAsync so to work around the blocking problem we'll have to create an non-blocking OrderBy method for IObservable.

public static class ObservableExtensions { public static IObservable<T> OrderBy<T, R>(this IObservable<T> that, Func<T, R> keySelector) { return new AnonymousObservable<T>( observer => { var orderBySubject = new OrderBySubject<T, R>(keySelector); var unsubscribe = orderBySubject.Subscribe(observer); that.Subscribe(orderBySubject); return unsubscribe; }); } public class OrderBySubject<T,R> : Subject<T> { private Func<T,R> keySelector { get; set; } public OrderBySubject(Func<T, R> keySelector) { this.keySelector = keySelector; } List<T> list = new List<T>(); public override void OnNext(T value) { list.Add(value); } public override void OnCompleted() { foreach (var item in list.OrderBy(keySelector)) { base.OnNext(item); base.OnCompleted(); } } } }

Now we can rewrite the code above asynchronously.

// snip... var concatenatedHtml = pageDownloads // Order by the result index .OrderBy(pageDownload => pageDownload.ResultIndex) // Grab the HTML .Select(pageDownload => pageDownload.Html) // Concatenate the HTML strings .Aggregate((accumulatedHtml, html) => accumulatedHtml + html) // Write the first (and only) result. .Subscribe(html => Debug.Write(html));

Of course I could've avoided the blocking problem by using the WebRequest object because it has Begin/End methods.  However the point of this article was to show that although the event-based asynchronous pattern can be converted to an IObservable the abstraction leaks.

Think Ahead When Designing Asynchronous APIs

If Rx is embraced (which I expect it will be) existing event-based asynchronous APIs will be more cumbersome to work with than those written using the alternative Begin/End Invoke pattern.  I ask developers to consider this when designing asynchronous APIs today.

About Me

My photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.