Blog

Using Hints for Generic MEF Exports

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

View Comments

  • Hey Jeremy, I sent out another post to you regarding this, but after looking at the code again here, I'm understanding it alot more. I'm assuming "MyClass" is a base class, then whatever object that is passed into the "ProcessClass" method, would be of that type, yes?
    My last post to you was about an issue I had with an abstract factory which I solved by having MEF new up a Func with the types I needed and pass this along to a factory method and so on...
    Back to this though. Let's say this "MyClass" in your example had a constructor with arguments, would the following still work:
    var service = Locator.GetServiceFor<MyClass>(); ?
    I had to go the Func route because .NET barked at the fact that one of my types was of an abstract type or had construcor with params, something to that effect. I used to have something similar to the following:
    var builder = factory.GetBuilder<Menu, MenuBuilder>();
    but .NET barked because MenuBuilder has args to it's constructor. So here is basically what I did. I let MEF new up a new Func as in:
    [Import]
    public Func<Menu, MenuBuilder> Menu {get;set;}
    so now I could then do this:
    var builder = factory.GetBuilder(Menu);
    The "GetBuilder" method looks something like this:
    public TBuilder GetBuilder<TView, TBuilder>
    (Func<TView, TBuilder> func)
    where TView : ViewComponentBase, new()
    {
    TView v = new TView();
    return func(v);
    }
    Of course, getting the Func to work required me Exporting a method that returned a new MenuBuilder. Returning func(v) above is what actually passes the argument into the builder. I learned that trick watching some other videos.
    Anyways, just wanted to share.
    Thanks for your great tutorials.
    David

  • So I typically don't find myself with classes that require constructor parameters. To me, classes require that for only three reasons (and I may be missing one, please let me know if I do) - first, to inject dependencies, second, to configure the class, and third, to provide some sort of parameters that are maintained by the state of the class to be used later.
    All of those are solvable. Since I know I'm using composition, I no longer inject dependencies, so I import dependencies. For configuration, I use a configuration class that abstracts the implementation details and again import that where it is needed.
    Finally, state and parameters can be passed via properties and methods.
    Therefore, I haven't run into the issue. If your constructor parameters are dependencies you can always using the [ImportingConstructor] tag.

  • Hi Jeremy,
    Thanks for the reply. I'm thinking I do not know enough about the configuration process then based off of your response. At any rate though, I have a solution for now that works with this abstract control factory Im working with. The only place I'm newing something up is inside the factory method as in:
    TView v = new TView();
    return func(v);
    and basically MEF is taking care of everything else.
    I will look into the configuration process more then, and will continue digging deeper into MEF as it's such an invaluable framework.
    Thanks much for your time.
    David W

Recent Posts

8-Step AWS to Microsoft Azure Migration Strategy

Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…

2 days ago

How to Navigate Azure Governance

 Cloud management is difficult to do manually, especially if you work with multiple cloud…

1 week ago

Why Azure’s Scalability is Your Key to Business Growth & Efficiency

Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…

3 weeks ago

Unlocking the Power of AI in your Software Development Life Cycle (SDLC)

https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…

1 month ago

The Role of FinOps in Accelerating Business Innovation

FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…

1 month ago

Azure Kubernetes Security Best Practices

Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…

2 months ago