Introduction
I was reading this article today, and following my motto "Laziness is the mother of invention", I immediately considered if I could achieve the same result, by leveraging existing .Net concurrent collections and my favorite AOP Toolkit. Don't get me wrong, there is nothing wrong with that article's approach, but I try not to reinvent the wheel as much as the next lazy bumm.
Background
Aspect Oriented Programming to me is one of those technologies, which I do not necessarily understand, but enjoy in everyday use. Like my car, internet backbone or Entity Framework. My favorite tool to simplify AOP is PostSharp, from the good folks at SharpCrafters. Furthermore, using ConcurrentBag and ConcurrentDictionary, is thread safe, and fast performing for most applications. So, the only challenge remaining is how to allow for background updates from different threads to occur on the GUI thread, while writing minimum amount of code by hand.
Both challenges are situations where the PostSharp Toolkits come quite handy. In this application we will use two of them.
Let's start by creating a new WPF application ... then open NuGet Package Manager Console and install the following packages:
Install-Package PostSharp.ToolKit.Domain
and
Install-Package PostSharp.ToolKit.Threading
This will add the AOP libraries to the default project. If you are GUI inclined, you can do the same by right clicking on a the solution node and selecting "Manage NuGet Packages for Solution ...".
I also like to use Reactive Extensions in my day-to-day, so lets add the Rx-Main package as well. This is not requied, it just makes creation of observable events more fun.
Install-Package Rx-Main
We will also be using Managed Extensibility Framework, so go ahead and add:
System.ComponentModel.Composition
Now for the fun part ...
Using the code
We will start by creating our Customer class:
using System;
using PostSharp.Toolkit.Domain;
namespace WpfWithAopAndPtl
{
[NotifyPropertyChanged]
internal class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotifyPropertyChangedSafe]
public string FullName
{
get { return String.Join(", ", LastName, FirstName); }
}
}
}
The [NotifyPropertyChanged] attribute is provided by the PostSharp Domain Toolkit. In short, it automatically handles the following scenarios:
- automatic and field-backed properties;
- complex properties directly or indirectly depending on fields and other properties of the same class;
- complex properties depending on methods within the same class (as long as they do not depend on methods in other classes);
- complex properties depending on properties of child objects providing change notifications (automatically in case of simple property chains, basing on explicit declaration of dependencies in case of arbitrarily complex code).
In other words, it eliminates a truck-load of code you'd otherwise have to write by hand. However, due to its few limitations (as of Nov'12) it will not handle:
- Property getters depending on methods on other objects (except cases mentioned above)
- Dependencies on external objects’ properties more complex than property getters invocation chains
- Properties depending on virtual methods
- Direct or indirect usage of delegates in property getters
- Dependencies on static fields
- Properties depending on items in collections
To provide support for those scenarios, it still allows you to implement INotifyPropertyChanged interface (and if you have another great tool, ReSharper from JetBrains, it will generate additional code to make the procedure much simpler).
To accommodate an observable thread-safe collection, I've opted to use a ConcurrentBag<T> and expose it using the IQueryable<T> interface. This way, the data is returned from the model as fast as possible, leaving sorting in the UI to some other class (ain't that SOLID ...).
Here is the model interface:
using System.Linq;
namespace WpfWithAopAndPtl
{
internal interface IMainViewModel
{
string Title { get; }
IQueryable<Customer> Customers { get; }
void Add(Customer c);
}
}
And the implementation of the model:
using System.Collections.Concurrent;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using PostSharp.Toolkit.Domain;
using WpfWithAopAndPtl.Annotations;
namespace WpfWithAopAndPtl
{
[Export(typeof (IMainViewModel))]
[NotifyPropertyChanged]
internal class MainViewModel : IMainViewModel, INotifyPropertyChanged
{
private readonly ConcurrentBag<Customer> _customers;
public MainViewModel()
{
_customers = new ConcurrentBag<Customer>();
}
public string Title
{
get { return "AOP Sample"; }
}
[NotifyPropertyChangedIgnore]
public IQueryable<Customer> Customers
{
get { return _customers.AsQueryable(); }
}
public void Add(Customer c)
{
_customers.Add(c);
OnPropertyChanged("Customers");
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
So again, I am applying the NotifyPropertyChanged attribute, than declare the public Customers property to be excluded from AOP processing, and in the Add() method, manually raise a OnPropertyChanged event. I also decorated the model with the Export attribute, so later I can use MEF to discover it.
Let's take a look at the main window's code behind:
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using PostSharp.Toolkit.Threading;
namespace WpfWithAopAndPtl
{
public partial class MainWindow : Window
{
private readonly Random _rand = new Random(DateTime.Now.Millisecond);
[Import(typeof (IMainViewModel))] private IMainViewModel _model;
private bool _shuttingdown;
public MainWindow()
{
InitializeComponent();
SatisfyImports();
DataContext = _model;
Loaded += MainWindow_Loaded;
Closing += (sender, args) => { _shuttingdown = true; };
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
IObservable<long> timer = Observable.Interval(TimeSpan.FromSeconds(1));
using (timer.Subscribe((s) => AddCustomer()))
{
while (!_shuttingdown)
{
Thread.Sleep(50);
}
}
});
}
[DispatchedMethod]
private void AddCustomer()
{
var customer = new Customer
{
Id = _model.Customers.Count(),
FirstName = Names.FirstNames[_rand.Next(Names.FirstNames.Length)],
LastName = Names.LastNames[_rand.Next(Names.LastNames.Length)]
};
_model.Add(customer);
}
private void SatisfyImports()
{
var catalog = new AssemblyCatalog(typeof (MainWindow).Assembly);
var container = new CompositionContainer(catalog);
container.SatisfyImportsOnce(this);
}
}
}
First, I declare that I will be asking MEF to provide me with a class, which implements the IMainViewModel interface. The import is satisfied during the execution of SatisfyImports method, where I simply ask for the current assembly to be used as a catalog of all my parts. Now, in a real production scenario, you could use a CompositeCatalog together with DirectoryCatalog, and mix classes from your current assembly, with classes found in some DLLs. Next, I set up the context of the window to the newly discovered model and proceed with a creation of Rx-based timer, which every second fires off an event causing a randomized Customer to be created and added to the collection, using model's public Add() method. The AOP's DispatchMethod attribute simply allows for the update of the UI to be executed from a background thread. Otherwise, WPF would generate an exception if the method was not executed on the GUI thread.
To expose the dynamic collection of customers, I opted to use a simple ListBox, with a DataTemplate.
<Window x:Class="WpfWithAopAndPtl.MainWindow"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>" xmlns:wpfWithAopAndPtl="clr-namespace:WpfWithAopAndPtl"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="{Binding Title}" Height="350" Width="525">
Following the SOLID principles of OO desing I leave it up to the data consumer to sort the collection of Customers as needed. There are many controls out there, like Telerik or Infragistics, which will also allow for additional functionality, for example filtering, or data virtualization. So exposing the collection as IQueryable makes sense IMHO.
And that's pretty much all it takes to have a multithreaded observable collection exposed in WPF.
Enjoy the attached code and submit your comments.