IContextAware services to bridge the gap between the View and the ViewModel – MEFedMVVM Part 3

Some content may be out of date. See latest blog on changes made here.

If you did not have look at MEFedMVVM introduction please so here. I also wrote an article on ExportViewModel here.

So what the hell is an IContextAwareService??

It is a service that knows about its context (which is the UIElement that invoked the import for the ViewModel and the services it depends on).

As I said in previous posts this is one of my favorite feature that MEFedMVVM has. Why well many times I created attached properties to extend UI functionality so that I can do proper MVVM. Yet some times it does not feel right because there is still a gap. The ViewModel does not have control over what the attached property does. Usually this is solved by exposing commands from the ViewModel and then you have another attached property that invokes the command which is magically databound ad what not. An example of something like this is VSM support I did a while ago here.

Enter MEFedMVVM with it’s IContextAwareServices.

You create this kind of service when you want to do some thing to or with the UI Element using the ViewModel as its DataContext. For example let’s say I want to know when the UIElement is Loaded and Unloaded so that my ViewModel does something smart only when you are Loading and Unloading. (please note this is just one example and I choose this example just to show the technique)

So let’s start by creating a service to do this.

First thing we have to do is create a class that Implements IContextAware. So our class will look like this

   1: public class DefaultContainerStatus : IContainerStatus


   2: {


   3:    #region IContextAware Members


   4:


   5:    public void InjectContext(object context)


   6:    {


   7:    }


   8: }

As you can see this interface has a method called InjectContext. This method will be called by MEFedMVVM with the UIElement that is requesting the ViewModel that has this service as Dependency. Since now we have the instance to the UIElement we can hook to the Loaded and Unloaded event and raise our own events that can later be mocked when unit testing the ViewModel.

So let’s create an interface to hide the default implementation of our service

   1: public interface IContainerStatus : IContextAware


   2: {


   3:     event Action ContainerLoaded;


   4:     event Action ContainerUnloaded;


   5: }

and now make the service implement that interface

   1: [ExportService(ServiceType.Both, typeof(IContainerStatus))]


   2: public class DefaultContainerStatus : IContainerStatus


   3: {


   4:     #region IContainerStatus Members


   5:


   6:     public event Action ContainerLoaded;


   7:


   8:     public event Action ContainerUnloaded;


   9:


  10:     #endregion


  11:


  12:     #region IContextAware Members


  13:


  14:     public void InjectContext(object context)


  15:     {


  16:         var element = context as FrameworkElement;


  17:         if (element != null)


  18:         {


  19:             element.Loaded += new RoutedEventHandler(ElementLoaded);


  20:             element.Unloaded += new RoutedEventHandler(ElementUnloaded);


  21:         }


  22:     }


  23:


  24:     void ElementLoaded(object sender, RoutedEventArgs e)


  25:     {


  26:         if (ContainerLoaded != null)


  27:             ContainerLoaded();


  28:     }


  29:


  30:     void ElementUnloaded(object sender, RoutedEventArgs e)


  31:     {


  32:         if (ContainerUnloaded != null)


  33:             ContainerUnloaded();


  34:     }


  35:


  36:     #endregion


  37: }

As you can see here all I am doing is handle the Loading and Unloading of the UIElement and raise the IContainerStatus events. In the code about I am also Exporting the service by using the ExportService attribute which tells MEFedMVVM that this is a service that can be consumed by ViewModels.

And that’s it folks. We are done. Now a ViewModel can start using this service by simple requesting it from the ExportViewModel attribute like so

   1: [ExportViewModel("VM1", typeof(IContainerStatus)]


   2: public class TestViewModel : BaseViewModel

One thing you should not forget is putting the [assembly: DesignTimeCatalog] somewhere in your project, otherwise MEFedMVVM will ignore your project. This is something that is not used at runtime but for design time it is crutial so that MEFedMVVM only loads your projects. MEFedMVVM will ignore any assembly at design time that does not have this attribute set!

Conclusion

There is a lot of potential in this approach. Currently MEFedMVVM exposes some out of the box IContextAware services which are

IViewStateManager – You can use this to invoke Visual States from your ViewModel.

IDispatcher – You can use this service to invoke delegates on the UI thread

IContainerStatus – You can use this service to get loaded and unloaded events.

and many more are coming up. But hey build your own if MEFedMVVM does not give you one already, it’s easy 🙂 and yea we would love to see what you come up with, if you want to contribute we are always happy to have your code 🙂

As always feedback is most appreciated.

Go MEF it up now 🙂 http://mefedmvvm.codeplex.com/

Leveraging MEFedMVVM ExportViewModel – MEFedMVVM Part 2

Some content may be out of date. See latest blog on changes made here.

In this post I will explain and show case what you can do with your VieModel by leveraging MEFedMVVM. If you want an introduction to MEFedMVVM please go here.

In order to make your ViewModel discoverable you simple decorate the ViewModel with ExportViewModel and give your view model a name so that the View can import the ViewModel by name. Some thing like this

ViewModel

   1: [ExportViewModel("VM1")]

   2: public class MyViewModel

and the view can look for this view model like this

   1: <UserControl x:Class="MEFedMVVMDemo.Views.UsersScreen"

   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:              xmlns:meffed="http:\\www.codeplex.com\MEFedMVVM"

   5:              meffed:ViewModelLocator.ViewModel="VM1">

The ViewModelLocator.ViewModel will leverage MEF to go get the exported ViewModel and set an instance of that ViewModel as the UserControl’s DataContext. BUT there is more stuff you can do.

 

DesignTime Data

The best way how to have design time data is by having Data Services injected in the ViewModel so that at design time you inject design time services and at runtime you inject the real services. This also allows you to better unit test because you can mock the data services while unit testing.

MEFedMVVM allows you to easily export services so that you can then import these services in the ViewModel. When you are exporting the service you also supply MetaData which is used by MEFedMVVM to decide which service to inject. Let’s do a quick example. Let’s say your ViewModel is using a service that implements IUserService to get a list of user from a database at runtime and you create another implementation of this service for design time that just returns a static list of Users. you would export these services like this

Design time Service

   1: [ExportService(ServiceType.DesignTime, typeof(IUsersService))]

   2: public class DesignTimeUsersService : IUsersService

   3: {

   4:    #region IUsersService Members

   5:  

   6:    public IList<Models.User> GetAllUsers(){}

   7: }

Runtime Service

   1: [ExportService(ServiceType.Runtime, typeof(IUsersService))]

   2: public class UsersService : IUsersService

   3: {

   4:    #region IUsersService Members

   5:  

   6:    public IList<Models.User> GetAllUsers()

   7: }

As you can see all you do is decorate the service with the Export Service attribute and specify if its Runtime or DesignTime. You can also specify ServiceType.Both if you want that service to be injected both at runtime and design time. I will talk more on the Export Service and what you can do with it in my next post. Just to give you a quick insight of what this can do here is a small list

– Have shared services (a service that is shared for all ViewModels)

– Have prioritized Services (have a service priority and MEFedMVVM will pick the appropriate service to be injected)

– Have IContextAware services. These are special UI related services. Basically MEFedMVVM will call InjectContext passing the UI element that requested the ViewModel, so that service can work as a bridge for the ViewModel and the View without any coupling. (This is one of my favorite features)

OK let’s not get side tracked. Were are we?

We have a ViewModel that is an empty class decorated with ExportViewModel and we have two services exported one for runtime and one for design time.

In order to inject the services we need in our ViewModel, we need to state that in the ExportViewModel attribute, like this

   1: [ExportViewModel("VM1", typeof(IUsersService))]

   2: public class TestViewModel {}

This will tell MEFedMVVM to get that service and inject it to the ViewModel. But how can MEFedMVVM inject this in the ViewModel? Well in order to do so your ViewModel must implement an interface called IServiceConsumer. Here is the interface contract.

   1: /// <summary>

   2: /// Defines a service consumer.

   3: /// An entity that has a Service Locator and consumes services

   4: /// </summary>

   5: public interface IServiceConsumer

   6: {

   7:     /// <summary>

   8:     /// Gets the service locator that contains the services 

   9:     /// </summary>

  10:     IServiceLocator ServiceLocator { get; }

  11:     

  12:     /// <summary>

  13:     /// Call to be made when services are injected to the ServiceLocator

  14:     /// </summary>

  15:     void OnServicesInjected();

  16: }

So you will have a ServiceLocator property from where you can get the Services injected from and you need a method called OnServicesInjected which will be called when MEFedMVVM injects the services.

Don’t worry if you do not want to implement the interface yourself we made the heavy lifting for you. You can inherit from BaseViewModel which implements this interface for you. BaseViewModel also implements the INotifyPropertyChanged so you can do PropertyChanged notifications by calling OnPropertyChanged( () => Myproperty); Yes the implementation of INotifyPropertyChanged we used leverages lambdas so that you do not have spelling mistakes when doing property changed notifications 🙂

Back to service injection…. So our ViewModel is not implementing IServiceConsumer or inheriting BaseViewModel; inside the OnServicesInjected you can get the services and get the data you need. Like this

   1: protected override void OnServicesInjectedOverride()

   2: {

   3:     foreach (var item in GetService<IUsersService>().GetAllUsers())

   4:     {

   5:         if (SelectedUser == null)

   6:             SelectedUser = item;

   7:         users.Add(item);

   8:     }

   9: }

So here I am getting the data from the IUsersService that was injected and adding them in a collection which is bound in the View.

So yea finally my designer can see this in Blend

image

This sounded like a lot of work because I went into details of how everything works. But try it and you’ll see how easy it is to do. If its not easy enough get back to me and I really want to hear your feedback.

What if I do not have a service injected but I still want design time data??

Yes, I agree. There are some ViewModels that do not have a service injected BUT they still want design time data. An example of this… Let’s say you have another ViewModel which renders the SelectedUser which the end user selects from the other ViewModel. This ViewModel does not have a service because the data is coming from either from a Mediator callback or from a Property that is bound to some UI…. How can I have Design time data for this ViewModel?

MEFedMVVM let’s you do this as well. All you need to do is implement the IDesignTimeAware. This is the interface

   1: /// <summary>

   2: /// Interface to be implemented by ViewModels that want to do something when rendering in design time

   3: /// </summary>

   4: public interface IDesignTimeAware

   5: {

   6:     void DesignTimeInitialization();

   7: }

The DesignTimeInitialization method will be called on your ViewModel (ONLY AT DESIGN TIME) and inside there you can do what you want in order to have Design Time Data. Here is an example

   1: [ExportViewModel("VM2", typeof(IMediator), typeof(IVisualStateManager))]

   2: public class SelectedUserViewModel : BaseViewModel, IDesignTimeAware

   3: {

   4:     private User selectedUser;

   5:  

   6:     /// <summary>

   7:     /// Gets or sets the selected user

   8:     /// </summary>

   9:     public User SelectedUser

  10:     {

  11:         get { return selectedUser; }

  12:         set

  13:         {

  14:             selectedUser = value;

  15:             OnPropertyChanged(() => SelectedUser);

  16:         }

  17:     }

  18:  

  19:     protected override void OnServicesInjectedOverride()

  20:     {

  21:         mediator = GetService<IMediator>();

  22:         if (mediator != null)

  23:             mediator.Register(this);

  24:     }

  25:  

  26:     [MediatorMessageSink(MediatorMessages.SelectedUser, ParameterType=typeof(User))]

  27:     public void OnSelectedUserChanged(User selectedUser)

  28:     {

  29:         SelectedUser = selectedUser;

  30:     }

  31:  

  32:  

  33:     #region IDataContextAware Members

  34:  

  35:     public void DesignTimeInitialization()

  36:     {

  37:         SelectedUser = new User

  38:         {

  39:             Name = "Marlon", Surname = "Grech", Age = 24

  40:         };

  41:     }

  42:  

  43:     #endregion

  44: }

This will render like this in Blend

image

What if the DataContext is set because I am in a DataTemplate? How can I get Services injected and some design time data as well?

Very well. Sometimes we have scenarios when you have a DataTemplate that has a UserControl inside. This UserControl will still have a ViewModel set as its DataContext but it must not be set from MEFedMVVM ViewModelLocator.ViewModel attached property since the ContentControl or the ItemsControl should be setting the DataContext.

You can still do this with MEFedMVVM! In the ExportService you can have an additional parameter stating that this ViewModel should be “DataContextAware”. Something like this

   1: [ExportViewModel("UserVM", true, typeof(IMediator))]

   2: public class UserViewModel : BaseViewModel, IDesignTimeAware

   3: {

The second parameter passed set to true is where you specify that this ViewModel needs to be “DataContextAware”.

What is this gonna do?

Well at Runtime it will not create an instance of the ViewModel, instead it will wait until the DataContext is set on the UIElement. When DataContext is set, it will inject the services that you requested in the ExportViewModel.

At DesignTime it will create the ViewModel instance and inject it in the DataContext of the UI element so that you can still design your UI seamlessly.

Conclusion

These are only some of the features that MEFedMVVM has to offer. We are working on expanding more functionality. We are also working hard to test as many scenarios as possible but without your feedback and help we cannot cover all of them.

Please let us know if you encounter any issues so that together we can continue building an awesome and easy to use library, and have loads of fun creating cool WPF and SL applications 🙂

Download the source

http://mefedmvvm.codeplex.com/

Peace