Hibernating Rhinos

Zero friction databases

Model Attacher pattern in Silverlight applications

A while ago, when we started to develop our next version of RavenDB Studio, one of our goals was to make its code as simple as possible. That way, we ensure that it is easy to understand what is going on, so making changes to the Studio should be a trivial task.

In order to achieve that, we decided to not use any MVVM toolkits, but simply use a simple pages (views) and attach a model to them. In this approach, every view (page) know how to resolve its view-model by itself. This makes the Silverlight code much more simple, since it let’s you open a specific view by just navigating to its relative URL.

In order to make this possible we have a ModelAttacher.AttachModel attached property on the page, which takes care of instantiating the view-model and attach it to the page’s DataContext property.

Take a look on the following tipical view (page) in order to see it in action:

<Infrastructure:View x:Class="Raven.Studio.Views.Home"
                     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:Infrastructure="clr-namespace:Raven.Studio.Infrastructure"
                     Title="Home"
                     Style="{StaticResource PageStyle}"
                     Infrastructure:ModelAttacher.AttachModel="HomeModel">

</Infrastructure:View>

In this example, we have an empty Home view, which is make a use of a HomeModel. The ModelAttacher’s job here is to create an instance of the HomeModel class and attach it to the View.DataContext property. (The view is a simple class that derives from Page.)

This is how ModelAttacher works, in order to achieve this:

namespace Raven.Studio.Infrastructure
{
    public static class ModelAttacher
    {
        public static readonly DependencyProperty AttachModelProperty =
            DependencyProperty.RegisterAttached("AttachModel", typeof(string), typeof(ModelAttacher), new PropertyMetadata(null, AttachModelCallback));
        
        private static void AttachModelCallback(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            var typeName = args.NewValue as string;
            var view = source as FrameworkElement;
            if (typeName == null || view == null)
                return;

            var modelType = Type.GetType("Raven.Studio.Models." + typeName) ?? Type.GetType(typeName);
            if (modelType == null)
                return;

            try
            {
                var model = Activator.CreateInstance(modelType);
                view.DataContext = model;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(string.Format("Cannot create instance of model type: {0}", modelType), ex);
            }
        }

        public static string GetAttachModel(UIElement element)
        {
            return (string)element.GetValue(AttachModelProperty);
        }

        public static void SetAttachModel(UIElement element, string value)
        {
            element.SetValue(AttachModelProperty, value);
        }
    }
}

Now in order to attach a model to its view, we need to just add the attached property to the view element:  Infrastructure:ModelAttacher.AttachModel="HomeModel".

Please note that in this case any view-model has to have a default (parameters-less) constructor. In order to solve that, what we have done in RavenDB Studio is to have a ModelBase class for our view-models which make sure to expose all our common model dependencies.