Wednesday, September 2, 2009

Caliburn = Less Code?

Caliburn is an application framework for WPF / Silverlight. If you’re developing applications for these platforms, there are many reasons why you definitely need to be using this framework, but today, I noticed how using this framework resulted writing less code while doing more. Let’s see how writing a small WPF application using MVVM pattern is different when using Caliburn.
Note: There are many other ways to skin a cat. This is how I do things, there may be other, better ways to do the same thing.

Application Startup
When creating an application using MVVM and “ViewModel First” development, you’d create the root View and VM, bind them through DataContext and show the main window of the application.

///
/// Interaction logic for App.xaml
///
public partial class App : Application
{
   protected override void OnStartup(StartupEventArgs e)
   {
      base.OnStartup(e);

      var container = new WindsorContainer();
      var rootView = new RootView();
      var rootModel = new RootViewModel();

      rootView.DataContext = rootModel;
      this.MainWindow = rootView;
      this.MainWindow.Show();
   }
}
With caliburn, all you need to do is to create the root VM. There’s no trace of any View creation and binding the DataContext to VM. All this is done automatically by Caliburn. To do this, use “CaliburnApplication” as your base application class which will configure Caliburn framework upon startup. There are other ways to do the configuration but let’s stick to this simple configuration scenario that installs default implementations of Calburn “Components”.
///
/// Interaction logic for App.xaml
///
public partial class App : CaliburnApplication
{
   protected override IServiceLocator CreateContainer()
   {
      var container = new WindsorContainer();
      var adapter = new WindsorAdapter(container);

      return adapter;
   }

   protected override object CreateRootModel()
   {
      return new RootViewModel();
   }
}
One thing a little different is that Caliburn uses IServiceLocator insterface (from Common Service Locator) and has no direct dependency on a vendor specific IoC Container, which is a good thing. This means, you can choose various IoC containers and adapters for major IoC containers like Windsor, Ninject, Autofac, etc. are already included in Caliburn bits. If you're not a fan of IoC containers and won't be using one in your application (now, come on!), Caliburn uses a lightweight built-in container, in case you don't specify any other.

Actions and Commands
Should you bind an action to a ICommand instance on the VM, in pure MVVM application you would bind the Command property to an ICommand instance on the VM. You can specify the condition on which the command will be executable and you’ll also implement the action execute by the command.
<Button Command="{Binding ExitCommand}" Content="Exit Application">

public class RootViewModel : BaseViewModel
{
   private ICommand _exitCommand;
   public ICommand ExitCommand
   {
      get
      {
         if (_exitCommand == null)
           _exitCommand = new RelayCommand(this.Exit, this.CanExit);

         return _exitCommand;
      }
   }

   public bool CanExit()
   {
      return true;
   }

   public void Exit()
   {
      Application.Current.Shutdown();
   }
}
Notice there are other “hidden” code here too. You’ll need to create a new Command, or use RelayCommand (kudos to Josh) and you probably need a common layer supertype that implements INotifyPropertyChanged interface. Using Caliburn you'd bind the action directly to the VM and there's no dependency to ICommand instances. The good thing is, you’re not limited to Command property anymore and you can bind various other events such as Click, MouseOver, etc. to actions on your VM.
<Button cal:Message.Attach="[Event Click] = [Action Exit]" Content="Exit Application">

public class RootViewModel : Presenter
{
   public bool CanExit()
   {
      return true;
   }

   public void Exit()
   {
      Application.Current.Shutdown();
   }
}
There are lots of existing boilerplate functionalities already available in Caliburn, so you can drop BaseViewModel and use Presenter class (IPresenter implementation).

Displaying Views
To display other views in the RootView (e.g. Shell), we’d constructed a new VM and set it to a Content property of a ContentControl. This will actually pick up the view specified in the DataTemplate from Application Resources, and display it on the UI. You have to maintain a mapping between your Views and your VMs in a resource dictionary and as your application grows this gets harder to maintain.
<Application.Resources>
   <DataTemplate DataType="{x:Type vm:CustomerViewModel}">
      <view:CustomerView />
   </DataTemplate>
</Application.Resources>

<ContentControl Content="{Binding Path=CurrentView}"/>

public class RootViewModel : BaseViewModel
{
   public void ShowCustomer()
   {
      CurrentView = IoC.Resolve<Customerviewmodel>();
   }

   private BaseViewModel _currentView;
   public BaseViewModel CurrentView
   {
      get { return _currentView; }
      set
      {
         _currentView = value;
         RaisePropertyChanged("CurrentView");
      }
   }
}
Let’s see how we can do this using Caliburn. First off, we do not need to maintain any ViewModel mapping if we name our Views and ViewModels according to the Caliburn Conventions that is, your view names should ending in View, presenters / VMs ending in “ViewModel” or “Presenter” and each should be in a separate namespace, namingly “Views” and “ViewModels” or “Presenters”. This is just by convention and you can always override if you need to. Caliburn will automatically create the view for you, set the “CurrentPresenter” property on the main window (i.e. IPresenterHost) and do any required bindings. Did I mention you don’t need the whole mapping of View <-> DataTemplate in your Application / Form resource?
<ContentControl x:Name="CurrentPresenter" >

[Singleton("RootViewModel")]
public class RootViewModel : PresenterManager
{
   public void ShowCustomer()
   {
      var presenter = ServiceLocator.Current.GetInstance<Customerviewmodel>();
      this.Open(presenter);
   }
}
Conclusion
What’s the catch? How does Caliburn do this? Caliburn uses “Convention over Configuration” pattern, so if you do things according to the convention you’ll end up saving a lot of code. There’s always the possibility to change the default behavior too. By complying to these conventions, clearly you’ll end up writing less code, but more importantly the code you do write is cleaner, more robust and probably with better testabilty. With the help of “Binding Tests” utilities existing in Caliburn, you can test your bindings the easy way, which is some thought for later posts.
Submit this story to DotNetKicks Shout it

1 comment:

Anonymous said...

Very interesting post, looking forward to reading about testing.