Tuesday, October 26, 2010

Silverlight Cookbook: ViewModels, Coroutines and Binding Conventions

This post is part of a series of blog posts detailing various aspects of the Silverlight Cookbook, an initiative to demonstrate proper design choices for building enterprise-class line-of-business applications with Silverlight (and WPF if you will). It currently consists out of the following parts.

After having covered querying, commanding and the domain model, it is time for discussing some of the Silverlight application details. But before I do that, let's list the components I currently use:

Consider the client part of my reference architecture

clip_image001

Shell

The first essential part of every application is the Shell. It's a placeholder for all UI behavior related to menu bars, error messages, last-chance exception handling and the Master-page equivalent for Silverlight. Originally, I was planning to use the Regions and Views concept such as provided the Composite Application Guidance (CAG) block of Prism. But once I started working with the combination of Caliburn Micro (CM) and the Silverlight Business Navigation project template and added one of the awesome free themes, I never needed something like that anymore. As a nice bonus, the Business Navigation template already handles deep linking and back/forward support for you. And if you're building a Line-of-Business application, check out the new Jetpack theme. It's excellent for exactly that purpose.

If you don't want to use the Business Navigation template, CM offers a complete solution consisting of a Screen class representing the ViewModel of a XAML page or a user control, and a Conductor class that represents the Shell and allows activating and deactivating individual screens. The out-of-the-box conductor only allows having one active screen (aka SDI), but it is quite trivial to support an MDI-style application because the CM design is very extensible. For instance, the Screen class implements a whole bunch of interfaces that allows your ViewModel to hook in its life-cycle. Examples of those interfaces include IActivate, IDeactivate, IGuardClose and IChild. Notice that this approach is based on the ViewModel-first concept. In other words, you are always activating ViewModels where CM will use conventions to figure out which XAML page, XAML fragment or User Control to display.

In the case of the Business Navigation template, the conductor functionality is handled by a Silverlight Frame control. CM provides a FrameAdapter class that makes sure that the above mentioned life-cycle notifications are also applied to ViewModels that are associated with the XAML pages that the Frame control loads. As such CM supports both a ViewModel-first approach as well as View-first.

ViewModels

Obviously, no serious developer will build a Silverlight application without applying the Model-View-ViewModel pattern. Let's start with the code that is involved when loading a view. It's from The CQRS Kitchen demo and represent the ViewModel for a view that is used to search for recipes.

    public class SearchViewModel : Screen

    {

        [Dependency]

        public IApplicationController ApplicationController { get; set; }

 

        [Dependency]

        public IQueryAgent QueryAgent { get; set; }

 

        protected override void OnInitialize()

        {

            MinimumRating = 0;

            PartialDescription = "";

            PartialName = "";

        }

The first thing to notice is the base-class this ViewModel is deriving from. It extends CM's Screen class, which offers a way for raising the INotifyPropertyChange using type-safe expressions instead of using literal strings, and implements all the plumbing needed for hooking into the life-cycle interfaces CM offers. You don't necessarily need to derive your ViewModels from Screen and can implement those interfaces directly, but using the Screen class prevents your ViewModel from becoming too obscure.

The OnInitialize method is one of those life-cycle methods I mentioned before but is not doing anything useful in this particular case other than setting the defaults for the search fields. Notice the use of the NotifyOfPropertyChange method in the property setters below. That's the PropertyChangedBase class in action where Screen is deriving from. It will raise the corresponding NotifyPropertyChanged event, and make sure it is raised from the UI thread, regardless of the thread you called NotifyOfPropertyChange from.

        public string PartialName

        {

            get { return partialName; }

            set

            {

                partialName = value;

                NotifyOfPropertyChange(() => PartialName);

            }

        }

 

        public string PartialDescription

        {

            get { return partialDescription; }

            set

            {

                partialDescription = value;

                NotifyOfPropertyChange(() => PartialDescription);

            }

        }

 

        public double MinimumRating

        {

            get { return minimumRating; }

            set

            {

                minimumRating = value;

                NotifyOfPropertyChange(() => MinimumRating);

            }

        }

The code also shows some dependencies this particular ViewModel has on other application-specific services. In the reference architecture, the ApplicationController is responsible for navigating to other views or dialog windows and knows the URLs of their XAML files. This way I can verify the navigation behavior of the ViewModel without the need to know anything about the actual URL or the required query parameters.

In this demo application I use Unity to handle dependency injection, but CM can be extended to use any IoC framework by overriding the appropriate methods in the class that inherits from the Bootstrapper class:

    public class ClientBootstrapper : Bootstrapper<ShellViewModel>

    {

        private readonly IUnityContainer container = new UnityContainer();

 

        protected override void Configure()

        {

// Setting up the Unity container left out for brevity...

        }

 

        protected override object GetInstance(Type service, string key)

        {

            if (service == null)

            {

                service = GetViewModelTypeNameFromKey(key);

                key = "";

            }

 

            return container.Resolve(service, key);

        }

 

        private static Type GetViewModelTypeNameFromKey(string key)

        {

            var viewModelType = from type in typeof(ShellViewModel).Assembly.GetTypes()

                                where type.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase)

                                select type;

 

            return viewModelType.SingleOrDefault();

        }

 

        protected override IEnumerable<object> GetAllInstances(Type service)

        {

            return container.ResolveAll(service);

        }

 

        protected override void BuildUp(object instance)

        {

            container.BuildUp(instance.GetType(), instance);

        }

    }

Coroutines

So let's look at the code that searches for existing recipes.

        public IEnumerable<IResult> FindRecipes()

        {

            var coroutine = new FindRecipesCoroutine

            {

                PartialName = partialName,

                PartialDescription = partialDescription,

                MinimumRating = (int)(minimumRating * MaxRating)

            };

 

            yield return coroutine;

 

            Recipes = coroutine.Recipes;

        }

It should look rather unconventional, especially the usage of the yield keyword and the reference to the IResult interface in the return type declaration. This interface is part of CM and defined as:

    public interface IResult

    {

        void Execute(ActionExecutionContext context);

 

        event EventHandler<ResultCompletionEventArgs> Completed;

    }

You can easily deduce from this definition that a class implementing this interface is supposed to do something upon calling Execute and should notify the caller when that is finished. In essence, this is an asynchronous version of the Command Pattern, but most people refer to it as a Coroutine. CM will assume that all ViewModel methods that return an IEnumerable of IResult are doing asynchronous work and will ensure that the Execute method of a coroutine is not executed until the previous one has completed. The compiler will do the rest. Why?

Well, this is probably the first time I've actually found good use of the yield keyword. Because what happens here is that the compiler will generate a hidden class for the FindRecipes method. This class implements IEnumerable and breaks the method body into two steps that are executing sequentially as part of each IEnumerable.Next call. As such, the first step will construct the FindRecipesCoroutine class and return it. CM will then invoke its Execute method and wait until its Completed event has completed. Only then, it will continue enumerating and cause the code following the yield to execute. By returning multiple coroutines you can create a kind of mini-workflow because the compiler and CM will ensure everything is executed in the right order. That is awesome, isn't it!?

The coroutine itself is not that complex.

    public class FindRecipesCoroutine : SimpleCoroutine

    {

        [Dependency]

        public IQueryAgent QueryAgent { get; set; }

 

        public string PartialName { get; set; }

 

        public string PartialDescription { get; set; }

 

        public int MinimumRating { get; set; }

 

        public IEnumerable<Recipe> Recipes { get; internal set; }

 

        protected override void OnExecute()

        {

            QueryAgent.FindRecipes(MinimumRating, PartialName, PartialDescription,

                recipes =>

                {

                    Recipes = recipes.ToArray();

                    OnComplete();

                });

        }

    }

It's just a thin wrapper that calls a facade hiding the complexities (ugliness) of using WCF Data Services. And since we can rely on CM to inject any dependencies using the configure IoC container, even this class is easily testable. Notice the SimpleCoroutine template class. It's something I created myself to get rid of the repeating code required for declaring and raising the Completed event. The CQRS Kitchen demo also includes a ComplexCoroutine that allows using multiple yield return statements in the OnExecute method to wrap mini-workflows into a coroutine.

In this case, the Recipes property simply exposes the Recipe objects that were generated by the Add Service Reference context menu option of Visual Studio. Because those classes implement INotifyPropertyChanged, In most cases that is sufficient for all kinds of tracking purposes. However, sometimes you have to add some derived properties that are specific to the ViewModel. In usually extend the generated class and add whatever I need for my particular situation and postfix that class with Model. That signifies the fact that I see those classes a part of my ViewModel.

Binding Conventions

You may have noticed the lack of any logic that handles or returns instances of ICommand as you'll find in many other MVVM examples. In fact, I never explained how the XAML view gets attached to the ViewModel, and where this ViewModel instance is created in the first place. This is all part of CM's convention-based binding.

In short, regardless of whether you go for View-first or ViewModel-first, CM will ensure that the ViewModel instance is set as the DataContext of the view, that its dependencies are injected, and that the implemented lifecycle interfaces are called. Additionally, CM will automatically bind UI controls to the ViewModel properties based on the control's name. The specific control property and UI event part of that binding is defined by CM's ConventionManager class, and can be easily changed. Just check out the ClientBootstrapper on CodePlex.

The same applies to invoking methods on the ViewModel. By default, CM will relate buttons with a particular name to the corresponding equally named method (which may or may not use coroutines). It even supports passing the values of other UI elements as the parameters of that method, again by matching their names. And if your ViewModel has a boolean property or method with the same name, but prefixed with Can, then CM will use that to dynamically disable and enable the corresponding button. Morever, using event triggers, you can hook up methods to virtually any UI control event available.

Rob Eisenberg, the author of CM, has written some pretty elaborate articles demonstrating the power of his convention-based binding. Read the introduction first and then continue with the stuff about the actions.

Miscellaneous goodies

For a long while I have been wishing Patterns & Practices would develop a version of the Policy Injection Application Block suitable for Silverlight. But recently, a community member came up with a CM extension that looks similar to the Action Filters concept in ASP.NET MVC. With that you can apply attributes to your ViewModel properties and methods like [Async], [Rescue], [SetBusy], [Dependencies] or anything else you can come up with yourself. The CQRS Kitchen is not using this yet, but that’s one of things I plan to the next weeks.

Another little gem for which I'm quite happy CM provided a solution, is its IObservableCollection<T> interface and the corresponding BindableCollection<T>. Now it's finally possible to expose a collection that implements INotifyCollectionChanged, without exposing the actual collection class itself. I'm one of those guys that tries to restrict any collection properties to Ienumerable<T>, so having IObservableCollection is a great life saver. But it gets better. The BindableCollection will automatically raise its events from the UI thread, even if you add or remove items from a background thread.

Well, this post has ended up being a bit longer than I expected, and I’m not done yet, so keep checking back once in a while. Topics I have in mind include at least UI validation, the service agent and event aggregation.