WPF Application with Caliburn - Part Four
In computer science, coroutines are program components that generalize subroutines to allow multiple entry points for suspending and resuming execution at certain locations. Coroutines are well-suited for implementing more familiar program components such as cooperative tasks, iterators,infinite lists and pipes.
This feature is extremely useful if you do async programming like accessing data over a webservice or execution of long running tasks on the background thread. Let’s take a closer look on how to use this feature and how it helps us developing better applications and results in more maintainable code.
This post will work equally on Caliburn 1.1 and 2.0.
If you have seen other parts of Caliburn posts, Caliburn facilitates implementing MVVM by eliminating usage of ICommands. Forget about implementing ICommand interface or using RelayCommands. You can not directly bind your events to public actions on your ViewModel class. You no longer are limited by controls having commands, you can bind any arbitrary event to any public method on your ViewModel. Caliburn also checks for any existing preconditions based on the convention. The precondition, called Filters, are properties name the same as your action, but with a “Can” prefix. Here’s how it looks to bind:
<Button cal:Message.Attach="[Event Click] = [Action ShowCustomers]">
public virtual bool CanShowOrders
Your action that used to return void can return a special return type that will be handeld by Caliburn upon execution. If your method returns an instance of IResult interface it will be treated differently. First let us look at IResult interface:
public interface IResult
Very simple to implement! Basically, Execute method is called by Caliburn and this is where you implement the execution logic of this action. Caliburn then pauses unitl your action fires “Completed” event at which point it understands that action has compeleted and is finished. Where’s the power, you may ask? Well you can send multiple IResults instanced from your action method by returning an
IEnumerable<IResult> and since Caliburn waits for each action to fire its Completed event, all IResult instances are processed sequentially. An example is displaying a progress window, firing up a background worker to do a long running process data and stop progress window when the background worker is finished, let’s see how:
public IEnumerable<IResult> LoadOrders()
And the outcome is that the progress window is still showing until ServiceFetchResult has finished the long running procedure.
Remember how specially IResults are treated. In the above example, enumerable items are only processed if the previous one’s Completed event is fired. This helps a lot when you do async style programming, specially in Silverlight which almost every outgoing request should run asynchronously.
If you have used an async service, you know how painful it is when it comes down to testing. The problem is how the Async model works, and the easiest way is the same way you connect to webservices in Silverlight: First you subscribe to the event that is called when operation is completed, then you call the async method and wait for it to call you back on the callback handler. If you want to abstract it in an interface, it’d be something like this:
public class FetchOrdersCompletedEventArgs : AsyncCompletedEventArgs
The problem with this code which makes it unsuitable for testing is that you usually generate proxies for your services and this piece of generated code has numerous problems: there’s no clean interface and even generated methods are not virtual, making the whole thing unreplaceable in testing (at least by conventional tools). Notice that this implementation of async pattern which uses events is much cleaner than the one using IAsyncResult and callbacks, but still not much good in unit testing.
Now lets sit back and see what is it that we want to test? Let’s suppose we want to test that LoadOrders will load orders from the correct service operation, how would we do that?
This code would have worked unless you can’t mocked out the service that easily, remember? Now here’s how using IResult would help you test the same thing, but with a changed mindset: