Caliburn.Micro and a ViewModel with no base class.

As a general rule I try to use composition over inheritance. I will let Google do the talking on that point for me.

http://www.google.com/search?q=composition+over+inhetence

The problem is many frameworks build on .net encourage the opposite. For example almost every MVVM framework has a base ViewModel. Caliburn.Micro is one such framework. To avoid using this base class there are a few hoops to jump through.

INotifyPropertyChanged

Caliburn.Micro has a base class called PropertyChangedBase that provides INotifyPropertyChanged and strong typed property notification. The best way of solving this is with ILWeaving and NotifyPropertyWeaver. By manipulating IL both the OnPropertyChanged method and the calls to OnPropertyChanged (after property sets) can be injected. This allows me to start with a ViewModel like this

public class PersonViewModel : INotifyPropertyChanged
{
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
    public event PropertyChangedEventHandler PropertyChanged;

    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }
}

And have it compiled to this

public class PersonViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string givenNames;
    public string GivenNames
    {
        get { return givenNames; }
        set
        {
            if (value != givenNames)
            {
                givenNames = value;
                OnPropertyChanged("GivenNames");
                OnPropertyChanged("FullName");
            }
        }
    }
    private string familyName;
    public string FamilyName
    {
        get { return familyName; }
        set
        {
            if (value != familyName)
            {
                familyName = value;
                OnPropertyChanged("FamilyName");
                OnPropertyChanged("FullName");
            }
        }
    }
    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", GivenNames, FamilyName);
        }
    }
    public void OnPropertyChanged(string propertyName)
    {
        var propertyChanged = PropertyChanged;
        if (propertyChanged != null)
        {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Marshal events to the UI thread

PropertyChangedBase also marshals property change events to the UI. To replicate this functionality with NotifyPropertyWeaver we can add a class named PropertyNotificationInterceptor to our assembly

public static class PropertyNotificationInterceptor
{
    public static void Intercept(object target, Action onPropertyChangedAction, string propertyName)
    {
        Application.Current.Dispatcher.Invoke(onPropertyChangedAction);
    }
}

This will result in all property notification being redirected to this intercept method and hence dispatched to the UI thread.

For more information on PropertyNotificationInterceptor see http://code.google.com/p/notifypropertyweaver/wiki/NotificationInterception

Validation (IDataErrorInfo)

While Caliburn.Micro does not explicitly supply this functionality it is common that it is placed in a base class. To meet this requirement I prefer to use a Mixin approach supplied by Heredar (http://code.google.com/p/heredar/). This is a bit more complex and requires some more setup.

1. A class to construct my validator

public static class ValidationFactory
{
    private static Dictionary validators = new Dictionary();

    public static IValidator GetValidator(Type modelType)
    {
        IValidator validator;
        if (!validators.TryGetValue(modelType.TypeHandle, out validator))
        {
            var typeName = modelType.Name + "Validator";
            var type = Type.GetType(modelType.Namespace + "." + typeName, true);
            validator = (IValidator)Activator.CreateInstance(type);
        }

        return validator;
    }
}

This class takes a type and uses convention to construct a Validator.

2. A class to use as a template for the Mixin

This is the class that is merged in with our model class

public class ValidationTemplate : IDataErrorInfo
{
    private INotifyPropertyChanged target;
    public object Target
    {
        get
        {
            return target;
        }
        set
        {
            target = (INotifyPropertyChanged)value;
            validator = ValidationFactory.GetValidator(target.GetType());
            validationResult = validator.Validate(Target);
            target.PropertyChanged += Validate;
        }
    }

    private void Validate(object sender, PropertyChangedEventArgs e)
    {
        if (validator != null)
        {
            validationResult = validator.Validate(target);
        }
    }

    private IValidator validator;
    private ValidationResult validationResult;

    public string Error
    {
        get
        {
            return string.Join(Environment.NewLine, validationResult.Errors.Select(x => x.ErrorMessage)
                         .ToArray());
        }
    }

    public string this[string propertyName]
    {
        get
        {
            return string.Join(Environment.NewLine, validationResult.Errors.Where(x => x.PropertyName == propertyName)
                         .Select(x => x.ErrorMessage)
                         .ToArray());
        }
    }
}

I am using FluentValidation (http://fluentvalidation.codeplex.com/) to do the validation. However this approach could be used with most other validation frameworks

3. A flag attribute

This is used to tell Heredar where to inject the Mixin

public class ExtendWithAttribute : Attribute
{
    public ExtendWithAttribute(Type type)
    {
    }
}

4. A class to validate our model

public class PersonViewModelValidator : AbstractValidator
{
    public PersonViewModelValidator()  
    {
        RuleFor(x => x.FamilyName).NotEmpty();
        RuleFor(x => x.GivenNames).NotEmpty();
    }
}

5. Decorate the model with the attribute

#!cs
[ExtendWith(typeof(ValidationTemplate))]
public class PersonViewModel :INotifyPropertyChanged
{
    ...
}

This may seem like a lot of work but only steps 4 and 5 have to be repeated per ViewModel.

Setting up Heredar and NotifyPropertyWeaver

Instructions for setting up Heredar and NotifyPropertyWeaver can be found here http://code.google.com/p/heredar/wiki/Setup and here http://code.google.com/p/notifypropertyweaver/wiki/Setup .

Source Code

Available here http://code.google.com/p/simonsexperiments/source/browse/CaliburnMicroNoBaseClass

Posted by: Simon Cropp
Last revised: 05 Feb, 2012 10:42 AM History

Comments

No comments yet. Be the first!

No new comments are allowed on this post.