Browse by Tags

All Tags » locator   (RSS)

It is very common to have base classes and interfaces that use generic types to define standard behaviors across business applications. One challenge with the Managed Extensibility Framework is that it doesn't directly support generic exports and imports: in other words, there is no way to effectively do the following:

...
[ImportMany(AllowRecomposition=true)]
public IService<,>[] GenericService { get; set; }
...

While there is a GenericCatalog you can use, I wanted something a little more flexible and specific to the application.

The Example Service

Let's assume we have an example "service" interface that does something to an instance of a type. We define the interface like this:

public interface IService<T> 
{
   void DoSomething(T instance); 
}

The Locator

The goal is to have a composite application that automatically picks up services that support different types of T and a locator that easily gives us the instance we are looking for. The locator looks like this:

public interface IServiceLocator
{
    IService<T> GetServiceFor<T>();
}

This allows us to easily ask for the service, and do something with it:

var service = serviceLocator.GetServiceFor<MyClass>();
service.DoSomething(myInstance);

The problem with the locator is that we don't have a basic "generic" import for the various services, and we'd have to do a lot of dancing with reflection to parse out types as they became available in order to wire them in. In this case, I felt it was easier to come up with an intermediary class to facilitate finding the service.

Hinting Around

I call it a hint because it hints to the locator where to find the right service. The interface for a service hint looks like this:

public interface IServiceLocatorHint
{
    bool ServicesType<T>();
    IServiceInterface<T> GetServiceFor<T>();
}

As you can see, the hint has two methods. One determines whether or not the hint is capable of producing the service for a given type, and the other returns that service. Now, let's assume in a dynamically loaded module we implement the service contract for MyClass using a class called MyService. It looks like this:

[Export]
public class MyService : IService<MyClass> 
{
   void DoSomething(MyClass instance) 
   {
      Debug.WriteLine(instance.ToString());
   }
}

Notice I am exporting the service as the concrete type. Next, I build a simple hint:

[Export(typeof(IServiceLocatorHint))]
public class MyModuleHints : IServiceLocatorHint
{
    [Import]
    public MyService MyServiceInstance { get; set; }
 
    public bool ServicesType<T>()
    {
        return typeof(T).Equals(typeof(MyClass));
    }

    public IServiceInterface<T> GetServiceFor<T>()
    {
        if (ServicesType<T>())
        {
            return (IServiceInterface<T>) MyServiceInstance;
        }

        throw new NotSupportedException();
    }

}

Putting it all Together

Now that we have the service wired by MEF with all dependencies, and the hint wired as well, we can implement the locator class.

[Export(typeof(IServiceLocator))]
public class ServiceLocator : IServiceLocator
{
    [ImportMany(AllowRecomposition = true)]
    public IServiceLocatorHint[] Hints { get; set; }

    public IServiceInterface<T> GetServiceFor<T>()
    {
        var serviceHint = (from hint in Hints 
                        where hint.ServicesType<T>() 
                        select hint).FirstOrDefault();

        if (serviceHint == null)
        {
            throw new NotSupportedException();
        }

        return serviceHint.GetServiceFor<T>();
    }
}

The class is simple. As modules are loaded, the main list is recomposed to include any new hints that were found. When the user requests a service, it simply finds the hint that satisfies the type, then returns the corresponding service.

Using this flexible locator class is as simple as importing it, then asking for the service:

[Import]
public IServiceLocator Locator { get; set; }

public void ProcessClass(MyClass item)
{
   var service = Locator.GetServiceFor<MyClass>();
   service.DoSomething(item); 
}

If you have multiple services in a module, you can easily build a base class that uses a dictionary to register the instances and streamline the methods that check for support and return the instances. The power of MEF is that new services are easily discovered as plugins and extensions are loaded into the main application, and you can basically build an application around what you don't know yet, rather than having to constrain it based upon what you do know.

Jeremy Likness

Been working with a lot of customers using the MVVM pattern with MEF and the issue always comes up regarding how to marry views to view models. There have a been a few musings on this, from my own ViewModel locator pattern to the even more advanced (and elegant) locator proposed by John Papa et al.

This is another pattern you may find useful. Most of the time your views are used in conjunction with some sort of navigation paradigm, whether it uses the navigation framework provided by Silverlight or a custom region management-type solution. The nice thing about using MEF for these needs is that the Lazy operator ensures the control is not wired up until it is needed. By some simple "magic" if we are using this pattern, we can easily bind the view to the view model in a totally decoupled manner.

First, we simply create a custom attribute for the view and the corresponding interface to make it easy to extract meta-data with MEF:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ViewOfTypeAttribute : ExportAttribute 
{     
    public string TypeName { get; set; }
    
    public ViewOfTypeAttribute(string typeName) : base(typeof(UserControl))
    {
        TypeName = typeName; 
    }
}

public interface IViewOfTypeCapabilities 
{
    string TypeName { get; }
}

Now we have a "magic string" we can tag the view with. If you like, you can convert it to an enum and have it typed, but this gives us more flexibility for other modules we may not know about. So my contact view might get tagged like this:

[ViewOfType("Contact")]
public partial class ContactView : UserControl
{
   public ContactView() 
   {
      InitializeComponent();
   }
}

Now we just make another tag for our view model. Almost all implementations of MVVM have some sort of base view model to help with things like firing the INotifyPropertyChanged event and so forth. Therefore, it's easy to target an attribute to the view model like this:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ViewModelOfTypeAttribute : ExportAttribute 
{     
    public string TypeName { get; set; }
    
    public ViewModelOfTypeAttribute(string typeName) : base(typeof(ViewModelBase))
    {
        TypeName = typeName; 
    }
}

public interface IViewModelOfTypeCapabilities 
{
    string TypeName { get; }
}

Look familiar? It should, because that's how we route views to view models. This way, even if multiple views use the same view model, we can still bind them together. When I make my view model, I simply tag it for export like this:

[ViewModelOfType("Contact")] 
public class ContactViewModel : ViewModelBase 
{
}

Notice the string I passed is the same for the view and the view model. These must match for the binding to occur! Now, I'm purposefully leaving out the navigation implementation. There are many ways this might happen, from swapping a ContentControl to using an ItemsControl and simply switching visibility. At some point, you will instantiate a copy of your view. When that happens, you can automatically bind the viewmodel if it is available. Here's a snippet that should give you the general idea:

[Export]
public class Binder  
{
   [ImportMany(AllowRecomposition=true)]
   public Lazy<UserControl,IViewOfTypeCapabilities> Views { get; set; }

   [ImportMany(AllowRecomposition=true)]
   public Lazy<ViewModelBase,IViewModelOfTypeCapabilities> ViewModels { get; set; }

   private _Bind(string binding) 
   {
      var viewModel = (from vm in ViewModels where vm.Metadata.TypeName.Equals(binding) select vm.Value).FirstOrDefault();
      
      foreach(var view in (from v in Views where v.Metadata.TypeName.Equals(binding) select v.Value).ToArray())
      {
         view.DataContext = viewModel;           
      }
   }
}

The _Bind command is simply called the first time the view is loaded onto a surface (added to a control, for example).

Now anytime you need to wire the two together, forget XAML or code behind or any of that nonsense. Just tag the view and the view model with a common attribute and they will get glued together for you!

Jeremy Likness