Advance Your Composite Application MVVM to the Next Level – Use Attributes to Automatically Populate Your View Model with Service Calls


Abstract

MVVM is a proven design pattern in WFP/Silverlight world. Its use helps achieve separation of concerns, frees UI from any logic to enhance testing, and generally makes the code easier to understand and maintain. In many LOB multi-tier applications MVVM is extended to include a presenter class. Presenter usually controls the view model, receives necessary services from dependency injection container and has some logic to populate the view model with business entities and/or reference data and send any changes back. The resulting code is usually pretty clean, easy to maintain and test. This article argues though that most of the code in such presenters to populate the view models is also very mechanical, routine, boring and can be avoided all together by introducing small attribute-based view model framework to declaratively populate view models from service calls and avoid extra coding, and, thus, avoid writing extra mechanical, routine, and boring tests.

If you prefer the “code first” approach VS 2008 solution can be downloaded here: MVVMAttributes.zip

The Base Line

Consider the following simplified application as our baseline: one of the application’s views is the “CustomerView” which gets customer information from the customer service, lets user edit the information (name and state of residency in our example) and allows saving the data back (omitted in our example). The following is the baseline Prism/MVVM implementation:

Business Entities

namespace MVVMAttributes.BusinessObjects
{
    public class CustomerData
    {
        public string Name { get; set; }
        public string State { get; set; }
    }
}

This class represents very simple transfer object to pass customer info between tiers.

View Model

public class CustomerViewModel : DependencyObject
{
    private readonly ObservableCollection<string> _states
        = new ObservableCollection<string>();

    // Customer model DP wrapper
    public CustomerData Customer
    {
        get { return (CustomerData)GetValue(CustomerProperty); }
        set { SetValue(CustomerProperty, value); }
    }
    public static readonly DependencyProperty CustomerProperty =
        DependencyProperty.Register("Customer", 
        typeof(CustomerData), typeof(CustomerViewModel));

    // Observable list of states
    public ObservableCollection<string> States
    {
        get { return _states; }
    }
}

This is a typical view model that encapsulates business model (Customer property) and reference data (States observable collection to present the user with the list of all states to pick from).

View

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="MVVMAttributes.Customer.CustomerView"
>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="First Name:"/>
        <TextBlock Text="State:" Grid.Row="1"/>
        <TextBox Text="{Binding Customer.Name}" 
                 Grid.Column="1"/>
        <ComboBox Grid.Row="1" Grid.Column="1" 
                  SelectedItem="{Binding Customer.State}" 
                  ItemsSource="{Binding States}"/>
    </Grid>
</UserControl>

The view binds its view model to the UI elements. In particular, it binds customer name to a text box (Text property) and customer’s state to a ComboBox (SelectedItem property). It also binds ComboBox’s ItemsSource to the full list of available states exposed by the view model. View contains no additional code (except implementing ICustomerView) so code behind file is not shown. This is ICustomerView interface:

namespace MVVMAttributes.Customer
{
    public interface ICustomerView
    {
        object DataContext { get; set; }
    }
}

Presenter

Presenter code is below. Code walk-through follows.

namespace MVVMAttributes.Customer
{
    public class CustomerPresenterWithServices
    {
        private readonly ICustomerView _view;
        private readonly ICustomerService _service;
        private readonly IReferenceDataService _refDataService;
        private CustomerViewModel _viewModel;

        public CustomerPresenterWithServices(
            ICustomerView view,
            ICustomerService service,
            IReferenceDataService refDataService)
        {
            _view = view;
            _service = service;
            _refDataService = refDataService;
        }

        public void ShowCustomer(int customerId)
        {
            _viewModel = new CustomerViewModel();
            _viewModel.Customer = _service.GetCustomer(customerId);
            ReplaceList(
                _viewModel.States,
                _refDataService.GetStates());

            _view.DataContext = _viewModel;
        }

        private void ReplaceList(
            ObservableCollection<string> list,
            string[] values)
        {
            list.Clear();
            foreach (var item in values)
            {
                list.Add(item);
            }
        }
    }
}

Presenter implementation is typical for MVVM designs with dependency injection frameworks. Constructor injects and caches the view, customer service, and reference data service. The ShowCustomer public method creates the view model, calls services to get domain model (customer data) and reference data. When view model is built up, it is assigned to view’s DataContext for data binding to take over.

As you can see the wiring code to cache services, call them and update the view model is nothing fancy, highly repetitive code, with a risk of being “cut-and-paste” in high volume in a real application with dozens of views/presenters and maybe hundreds of service calls. Now add a requirement to populate the view model asynchronously and the wiring code to do that will likely be repeated (and thus need to be tested) in all those presenters. This is where the view model framework I introduced in the abstract comes to rescue.

The View Model Attribute-based Framework

Attributes

Lets introduce the following two attributes that constitute the core of the declarative part of the framework:

namespace MVVMAttributes.Infrastructure
{
    public abstract class ServiceCallAttribute : Attribute
    {
        public Type ServiceType { get; set; }
        public string MethodName { get; set; }
        public string MethodParameters { get; set; }
    }

    [AttributeUsage(
        AttributeTargets.Property,
        AllowMultiple = false)]
    public class ServiceEntityAttribute : ServiceCallAttribute
    {
    }

    [AttributeUsage(
        AttributeTargets.Property,
        AllowMultiple = false)]
    public class ServiceListAttribute : ServiceCallAttribute
    {
    }
}

Two attributes (ServiceEntityAttribute and ServiceListAttribute) share the same base class that defines the type of the service to use, the method to call, and, if method has any parameters, the names to use to resolve the parameters values.

Attributed View Model

Now when we have the attributes we can amend our original model to declaratively specify how to get data to populate it:

namespace MVVMAttributes.Customer
{
    public class CustomerViewModel : DependencyObject
    {
        private readonly ObservableCollection<string> _states
            = new ObservableCollection<string>();

        // Customer model DP wrapper
        [ServiceEntity(
            ServiceType = typeof(ICustomerService),
            MethodName = "GetCustomer",
            MethodParameters = "customerId")]
        public CustomerData Customer
        {
            get { return (CustomerData)GetValue(CustomerProperty); }
            set { SetValue(CustomerProperty, value); }
        }
        public static readonly DependencyProperty CustomerProperty =
            DependencyProperty.Register(
            "Customer",
            typeof(CustomerData), typeof(CustomerViewModel));

        // Observable list of states
        [ServiceList(
            ServiceType = typeof(IReferenceDataService),
            MethodName = "GetStates")]
        public ObservableCollection<string> States {
            get { return _states; }
        }
    }
}

The “ServiceEntity” attribute on the “Customer” property specifies that this property should be populated by call to “GetCustomer” method on the “ICustomerService” service. The method expects one parameter and the caller will resolve its value using “customerId” name.

The “ServiceList” attribute labels the “States” property and states that this collection should be filled in using the “GetStates” method of the “IReferenceDataService” service. The method expects no parameters.

Now that view model is tagged with “service call” attributes we can use some generic factory to create and populate it. Once the code to populate view model is moved to a generic factory, our original presenter simplifies to this:

public class CustomerPresenter
{
    private readonly ICustomerView _view;
    private CustomerViewModel _viewModel;
    private readonly IViewModelFactory _viewModelFactory;

    public ICustomerView View { get { return _view; } }

    public CustomerPresenter(
        ICustomerView view,
        IViewModelFactory viewModelFactory)
    {
        _view = view;
        _viewModelFactory = viewModelFactory;
    }

    public void ShowCustomer(int customerId)
    {
        _viewModel = 
            _viewModelFactory.BuildViewModel<CustomerViewModel>(
            new Dictionary<string, object> { 
            { "customerId", customerId } });

        _view.DataContext = _viewModel;
    }
}

The presenter still injects the view but is no longer concerned with services and populating of its view model. Instead, it injects a generic view model factory to create the view model and “build it up” with service calls.

The magic behind it all – View Model Factory

The last and most interesting part of the framework is view model factory. It creates a view model, inspects its properties and their attributes using reflection, dynamically resolves necessary services, calls required methods and updates the view model. Below is the code and walk-though:

namespace MVVMAttributes.Infrastructure
{
    public interface IViewModelFactory
    {
        TViewModel BuildViewModel<TViewModel>(
            IDictionary<string, object> parameters);
    }
}

The above declares the public API for the view model factory. The view model is requested by specifying BuildViewModel’s generic type, and the “parameters” dictionary is used to resolve values for any parameters for the service calls necessary. Here is the implementation itself (partitioned to facilitate the walk-through):

namespace MVVMAttributes.Infrastructure
{
    public class ViewModelFactory : IViewModelFactory
    {
        private readonly IUnityContainer _container;

        public ViewModelFactory(IUnityContainer container)
        {
            _container = container;
        }

        public TViewModel BuildViewModel<TViewModel>(
            IDictionary<string, object> parameters)
        {
            TViewModel viewModel = _container.Resolve<TViewModel>();

            BuildUp<ServiceEntityAttribute>(
                viewModel, parameters, SetEntity);
            BuildUp<ServiceListAttribute>(
                viewModel, parameters, ReplaceList); 

            return viewModel;
        }

ViewModelFactory’s constructor injects the container, so it later can resolve requested services. The “BuildViewModel” method constructs the view model (again using the container) and “builds it up” by scanning for “ServiceEntityAttribute” first to populate “entities” and then for “ServiceListAttribute” to populate collection type data. Here is the code for “BuildUp<TAttribute>” method:

private void BuildUp<TAttribute>(
    object viewModel,
    IDictionary<string, object> parameterLookup,
    Action<PropertyInfo, object, object> setProperty)
    where TAttribute : ServiceCallAttribute
{
    var q = 
        from p in viewModel.GetType().GetProperties()
        from a in p.GetCustomAttributes(typeof(TAttribute), false)
        select new { Property = p, Attribute = (TAttribute)a };

    foreach (var item in q)
    {
        object result = CallService(
            item.Attribute, item.Property, parameterLookup);
        setProperty(item.Property, viewModel, result);
    }
}

A simple LINQ to reflection query is used to scan for properties marked for auto-population with service calls. For each found property specified service call is made by “CallService” and then view model is updated using one of the two “setProperty” strategies: property assignment or list replacement. Here is the “CallService” implementation:

private object CallService(
    ServiceCallAttribute attribute,
    PropertyInfo property,
    IDictionary<string, object> parameterLookup)
{
    Type serviceType = attribute.ServiceType;
    object service = _container.Resolve(serviceType);
    MethodInfo method = serviceType.GetMethod(attribute.MethodName);
    string[] parameterNames = attribute.MethodParameters != null ?
        attribute.MethodParameters.Split(',') :
        new string[] { };
    object[] parameters = parameterNames.Select(
        name => parameterLookup[name]).ToArray();

    return method.Invoke(service, parameters);
}

Here the service specified in the attribute is first resolved using the DI container. Then specified method from the attribute is called on this service using reflection. Any parameters are resolved against caller provided parameter lookup dictionary.

Conclusion

In the article we explored how custom attribute based frameworks can leverage composite application frameworks (like Prism) and MVVM pattern to remove any “wiring” code necessary to create and populate a view model in a typical n-tier application. We have created a simple declarative framework that takes care of instantiating required services, calling them, and automatically updating view model with service call results.

The framework we built is extremely simple to keep the article from bloating. There are several improvements and enhancements that can be applied to it, like:

  • Additional attributes to specify different types of service call results processing; in addition to property assignment and list replacing that we implemented, we can also have an attribute that specifies an optional translator between service call result and view model property
  • ViewModelFactory can take on responsibility to populate view model asynchronously; the benefit of this approach is that we only need to implement the asynchronous behavior once in the factory and none of the presenters need to change
  • To complete interaction with middle tier the framework should also provide a declarative way to submit modified business entities back using service calls; this can be achieved, for example, by mapping view model’s properties to “Save” service calls and providing declarative way to translate these properties into “Save” call parameters.

The complete solution (VS 2008) demonstrating the framework can be downloaded here: MVVMAttributes.zip

Advertisements
2 comments
  1. ross said:

    Interesting article, thank you. I like the concept but haven’t had time yet to digest the implementation. I too have worked on MVVM projects with large amounts of code wiring up viewmodels with views.

    As an aside, I was wondering why the Customer property on the CustomerViewModel is a DependencyProperty, and for that matter why is the CustomerViewModel a DependencyObject?

    Normally I would expect a plain vanilla INotifyPropertyChanged implementation would suffice?

    Cheers.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: