Click here to Skip to main content
15,884,388 members
Articles / Containers

Let’s Talk about MEF – Part 1

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
13 Nov 2018CPOL11 min read 6K   3   5
Let’s talk about MEF – Part 1

Introduction

So we talked about what Dependency Inversion and Injections are (see here), and last time we looked at how we can make our own IoC container.

I also promised we will start taking a look at the mature IoC containers and how they work, so the first one will be the Managed Extensibility Framework (MEF).

I do admit that I am a little biased towards this framework due to a number of reasons and I hope you will understand why once we get into discussing it. Also, please note that the sheer amount of information on MEF will turn this into a mini-series only about how to work with it. Otherwise, for this first post, we will have a look at the basics so that we have a basis to compare it against other frameworks.

What is MEF?

MEF is short for Managed Extensibility Framework and it’s been part of the .NET Framework since version 4, which for me means quite a bit because besides making it readily available without introducing a lot of 3rd party frameworks (not that there is something inherently wrong with them), it also means that it’s used behind the scenes. I don’t know if you happen to notice but in newer versions of Visual Studio (I think starting with 2013), when adding plugging Visual Studio will actually show a progress bar when loading that mentions that the plugins are loaded (and presumably created) with MEF.

One thing to note, doing some research online, MEF is not really in the IoC category of frameworks since it’s real purpose is intended for making applications extendable through plugins. That being said, I still use it as such, and when an application matures, I might even use it for its actual purpose.

So for us to better understand MEF which has a different terminology than other frameworks I encountered, we’re going to make a project based on a metaphor so that it will be easier to follow along both with the reason and the terminology.

Starting a Project with MEF

Writing the Blueprints

Let’s say you own a car factory that can create any car you wish, but there’s a catch, for your factory to work, it needs to have a blueprint of what you want it to create. So let’s write that first:

C#
namespace BlogPlayground
{
    internal class Car
    {

    }
}

I know, anti-climactic, but let’s start small and expand. Our car, like any other, will need a few essential parts, like for example wheels, so let’s make that a requirement:

C#
namespace BlogPlayground
{
    internal class Car
    {
        private const int WheelCount = 4;

        internal Car(WheelType wheelTypeType)
        {
            WheelType = wheelTypeType;
        }

        internal WheelType WheelType { get; }
    }
}

We’re going to assume that we’re using the same type of wheel for all 4 of them, we will create an additional class for that as well:

C#
namespace BlogPlayground
{
    internal class WheelType
    {
    }
}

On the technical side, the reason this is a class and not an enum (in case some people were asking that), is because we might want to add additional features to the wheels and also it plays nicely into our example :).

So now, we would have everything we need to make our factory working at the very basic level. But first, we will write it without MEF and then update it.

Building the Factory

Here’s how it would look without MEF:

C#
namespace BlogPlayground
{
    internal class Factory
    {
        internal Factory()
        {

        }

        internal Car CreateCar()
        {
            return new Car(new WheelType());
        }
    }
}

Now that we have written our factory, how about testing it out so we know we’re on the right track. As always, we will be using NUnit for this task:

C#
namespace BlogPlayground
{
    using NUnit.Framework;

    [TestFixture]
    public class FactoryTests
    {
        [Test]
        public void CarShouldHaveWheels()
        {
            Factory sut = new Factory();

            Car car = sut.CreateCar();

            Assert.That(car, Is.Not.Null, "car instance was not returned from the factory");
            Assert.That(car.WheelType, Is.Not.Null, 
                        "car instance should have an instance of wheel types");
        }
    }
}

We write the test, we make sure it passes and then we can continue without worrying about making mistakes.

Notice that the creation of a car and of its wheel type are hardcoded, that wouldn’t help us build any kind of car, right? So the first order of business is making our container.

For MEF to work, we need to add a reference to the System.ComponentModel.Composition assembly, this can be found by adding a new reference and looking in the Assemblies section.

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;

    internal class Factory
    {
        private readonly CompositionContainer _container;

        internal Factory()
        {
            AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            _container = new CompositionContainer(catalog);
        }

        internal Car CreateCar()
        {
            return _container.GetExportedValue<Car>();
        }
    }
}

Now let’s look at what we have done here:

  • MEF lives inside the System.ComponentModel.Composition.Hosting namespace, that’s why we added it on line 3.
  • MEF works on the concept of “catalogs”, basically a catalog tells MEF where to look for the blueprints and pieces it needs. The AssemblyCatalog we create on line 12 tells MEF to inspect all the types in the local assembly (of course, we could have provided another assembly if we wished, more on that later on).
  • On line 13, we created a CompositionContainer that received a catalog as its argument, this is the brains of the factory and we will see how it works in the next point.
  • On line 18, we tell the container that we want to return an object of type Car, this will make the container look through its catalog and find a blueprint for that type and the parts for it and will create a car for us.

Though if we were to run our test right now, we would get the following error:

No exports were found that match the constraint: \
ContractName BlogPlayground.Car\
RequiredTypeIdentity BlogPlayground.Car

Well, at least we now confirmed that the container is doing its job and tried to look up an object of type BlogPlayground.Car. We will need to help it out in finding that contract (blueprint in our analogy).

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [Export]
    internal class Car
    {
        private const int WheelCount = 4;

        internal Car(WheelType wheelTypeType)
        {
            WheelType = wheelTypeType;
        }

        internal WheelType WheelType { get; }
    }
}

So now, we added the using statement for MEF and also added an attribute called [Export] on line 5 that will tell MEF that this object is exposed in the catalog for creation.

Now if we were to run our test, we would get the following error:

System.ComponentModel.Composition.CompositionException : The composition produced 
a single composition error. The root cause is provided below. 
Review the CompositionException.Errors property for more detailed information.

1) Cannot create an instance of type ‘BlogPlayground.Car’ because a constructor 
could not be selected for construction. Ensure that the type either has a 
default constructor, or a single constructor marked with the 
‘System.ComponentModel.Composition.ImportingConstructorAttribute’.

Resulting in: Cannot activate part ‘BlogPlayground.Car’.\
Element: BlogPlayground.Car –> BlogPlayground.Car –> AssemblyCatalog 
(Assembly="BlogPlayground, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")

Now it fails because MEF required that when we create a type that doesn’t have a default or parameterless constructor, then the constructor should be marked as [ImportingConstructor] and because the constructor requires some additional parts, then we will need to mark the parameters with [Import] as well. So let’s do as the error tells us and fix that as well:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [Export]
    internal class Car
    {
        private const int WheelCount = 4;

        [ImportingConstructor]
        internal Car([Import]WheelType wheelTypeType)
        {
            WheelType = wheelTypeType;
        }

        internal WheelType WheelType { get; }
    }
}

Though if we try to run the test now, it will give us the exact same error as before about not finding a contract for the car. This is where MEF admittedly is a little annoying, what it should have told us now is that it cannot find a contract for the WheelType, so to fix this error, we’re going to update that class as well:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [Export]
    internal class WheelType
    {
    }
}

Now if we were to run our test, it would pass. But why the hassle of doing all of this when we could have just solved it with the hardcoded line? And I know it’s not all that different either since we are working with concrete classes. Well, you are right if you asked that, but let’s see now how we can truly tap into the power of MEF.

The Magic of MEF

First off, let’s make the Car class into an abstract because we would like to work with many different models:

C#
namespace BlogPlayground
{
    internal abstract class Car
    {
        private const int WheelCount = 4;

        internal Car(WheelType wheelTypeType)
        {
            WheelType = wheelTypeType;
        }

        internal WheelType WheelType { get; }
    }
}

Since we can’t instantiate an abstract class, we removed the attributes for MEF. Next, we will create a sports car:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [Export]
    class SportCar : Car
    {
        [ImportingConstructor]
        public SportCar([Import]WheelType wheelTypeType)
            : base(wheelTypeType)
        {
        }
    }
}

All well and good, but this won’t work because we want to create a Car but now we have a blueprint for a SportCar. To make it work, we need to tell MEF that this will be exported as a Car as well, to do that, we just specify the type in the attribute like so:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [Export(typeof(Car))]
    internal class SportCar : Car
    {
        [ImportingConstructor]
        internal SportCar([Import]WheelType wheelTypeType)
            : base(wheelTypeType)
        {
        }
    }
}

But just to make sure, we would want to add another test to make sure:

C#
[Test]
public void CarShouldBeASportCar()
{
    Factory sut = new Factory();

    Car car = sut.CreateCar();

    Assert.That(car, Is.Not.Null, "car instance was not returned from the factory");
    Assert.That(car, Is.TypeOf<SportCar>(), "the instance was not of type SportsCar");
    Assert.That(car.WheelType, Is.Not.Null, "car instance should have an instance of wheel types");
}

We write this test and it passes on the first try, and we made no change to the factory as well. For such a small code base, this doesn’t seem that impressive, but consider using this at the level of services and large applications, the power to change a whole behavior just from an attribute.

Let’s see about adding an engine to the Car but this time we will be using interfaces. 😉

Creating the Engine

First off, we will create an interface for our Engine. We also want to export anything of this type without declaring it explicitly for export. The interface will look like this:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [InheritedExport]
    internal interface IEngine
    {
    }
}

The [InheritedExport] attribute will export anything that inherits this class as an IEngine.

Now let’s update our Car with the new prerequisite:

C#
namespace BlogPlayground
{
    internal abstract class Car
    {
        private const int WheelCount = 4;

        internal Car(WheelType wheelTypeType, IEngine engine)
        {
            WheelType = wheelTypeType;
            Engine = engine;
        }

        internal WheelType WheelType { get; }

        internal IEngine Engine { get; }
    }
}

And since this is mandatory, we will need to update the SportCar as well:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [Export(typeof(Car))]
    internal class SportCar : Car
    {
        [ImportingConstructor]
        internal SportCar([Import]WheelType wheelTypeType, [Import]IEngine engine)
            : base(wheelTypeType, engine)
        {
        }
    }
}

Since the Engine is just an interface, we will also need to implement it, nothing fancy:

C#
namespace BlogPlayground
{
    class SportEngine : IEngine
    {
    }
}

Notice that as soon as this class was added, all the tests are passing again. Do note that an import and an export can only match one to one so if we were to add another engine, we would get an error telling us there are more Engines that the container doesn’t know what to do with them.

Though here’s a good spot to show where MEF outshines the competition if you will, and since a Car can’t have several engines, we’re going to move to the features section, so let’s add some features to our car.

Adding Features

First, let’s create an interface for the features:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [InheritedExport]
    public interface IFeature
    {

    }
}

Here we are going to do the same thing with the [InheritedExport] attribute so that we can extend the feature list easily in the future, so let’s create a few features for starters:

C#
namespace BlogPlayground
{
    using System.ComponentModel.Composition;

    [InheritedExport]
    public interface IFeature
    {

    }

    class USB : IFeature
    {
    }

    class GPS : IFeature
    {
    }

    class Radio : IFeature
    {
    }
}

So let’s update our car to accommodate these features:

C#
namespace BlogPlayground
{
    using System.Collections.Generic;

    internal abstract class Car
    {
        private const int WheelCount = 4;

        internal Car(WheelType wheelTypeType, IEngine engine, IEnumerable<IFeature> features)
        {
            WheelType = wheelTypeType;
            Engine = engine;
            Features = features;
        }

        internal WheelType WheelType { get; }

        internal IEngine Engine { get; }

        public IEnumerable<IFeature> Features { get; }
    }
}

And now for the implementation, watch the parameters carefully, the [Import] parameters can be satisfied by a single [Export], though an [ImportMany] parameter can be satisfied with more than one [Export]:

C#
namespace BlogPlayground
{
    using System.Collections.Generic;
    using System.ComponentModel.Composition;

    [Export(typeof(Car))]
    internal class SportCar : Car
    {
        [ImportingConstructor]
        internal SportCar([Import]WheelType wheelTypeType, 
        [Import]IEngine engine, [ImportMany] IEnumerable<IFeature> features)
            : base(wheelTypeType, engine, features)
        {
        }
    }
}

Again, the tests have passed but let us make sure that they are all there, let’s add another test:

C#
[Test]
public void CarShouldHaveThreeFeatures()
{
    Factory sut = new Factory();

    Car car = sut.CreateCar();

    Assert.That(car, Is.Not.Null, "car instance was not returned from the factory");
    Assert.That(car.Features, Is.Not.Null, "car instance should have a collection of features");
    Assert.That(car.Features, Has.Length.EqualTo(3), "the car should have 3 features");
    Assert.That(car.Features.ElementAt(0), Is.TypeOf<USB>());
    Assert.That(car.Features.ElementAt(1), Is.TypeOf<GPS>());
    Assert.That(car.Features.ElementAt(2), Is.TypeOf<Radio>());
}

As we ran this test, we will see that it passed as well. We have now created a way of adding features to our cars without modifying the car, all we need to do is create another feature and implement the IFeature interface.

This is just one of the many features that MEF provides, besides the fact that it’s also extensible, I want to show you one more thing before ending this post since MEF has a lot more to cover than just this, these are just the basics.

Multiple Cars?

Let’s say you like your new car so much you want to create one for your friends as well, let’s make an example of that through a new test:

C#
[Test]
public void ShouldBeAbleToCreateMultipleCars()
{
    Factory sut = new Factory();

    Car car1 = sut.CreateCar();
    Car car2 = sut.CreateCar();

    Assert.That(car1, Is.Not.Null, "car instance was not returned from the factory");
    Assert.That(car2, Is.Not.Null, "car instance was not returned from the factory");
    Assert.That(car1, Is.Not.EqualTo(car2), "the two instances should be different");
}

If we run this test, it will fail, and mostly because MEF and Dependency Injection, in general, is mostly thought of as having reusable swappable parts, as such when we call the factory to create a second instance, it will return the same instance, but we can change that, all we need to do is the following:

C#
namespace BlogPlayground
{
    using System.Collections.Generic;
    using System.ComponentModel.Composition;

    [Export(typeof(Car))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    internal class SportCar : Car
    {
        [ImportingConstructor]
        internal SportCar([Import]WheelType wheelTypeType, 
                          [Import]IEngine engine, [ImportMany] IEnumerable<IFeature> features)
            : base(wheelTypeType, engine, features)
        {
        }
    }
}

By adding the [PartCreationPolicy] attribute with a CreationPolicy.NonShared, we tell MEF that whenever it creates this part, it should create a new instance every time, considering back to our analogy, the axel between two wheels or the frame of the car should be shared, though each wheel should not be shared since we will have 4 of them.

The [PartCreationPolicy] can be applied to both [Import] and [Export] attributes and it can have 3 values and those are Shared, NotShared and Any (by default, if not specified it will be treated as Any) so the combinations between them need to match and the way they can match is as follows:

Import Export Instance
Shared Shared Single Instance
Shared NonShared No Match
Shared Any Single Instance
NonShared Shared No Match
NonShared NonShared Separate Instance
NonShared Any Separate Intance
Any Shared Single Instance
Any NonShared Separate Instance
Any Any Single Instance

Conclusion

I hope you enjoyed the small part of MEF that was presented here, in the future, we will look at other nifty features like (but not limited to) metadata, assembly discovery, function exports (yes, we can export even just strings and functions even), contracts and lazy initialization, custom exports and these come just out of the box without extending MEF at all.

Here are a few examples of how I (and others in the teams I worked in/with) have used MEF in the past and I’m curious what you come up with as well:

  • Making desktop application modules in different assemblies that would load when booting up.
  • Making applications that can download their modules in real time from the server and update without restarting the application.
  • Using it for enabling and disabling access to modules in an application based on roles or other criteria
  • Making functions that plug into the life cycle of the application without touching the core code.

Please note that this will not be a running series, but there will be the promised additions to it. I’m mentioning this since MEF might not be the thing you or others are looking for and it would just delay the presentations of the other frameworks.

Thank you and see you next time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Romania Romania
When asked, I always see myself as a .Net Developer because of my affinity for the Microsoft platform, though I do pride myself by constantly learning new languages, paradigms, methodologies, and topics. I try to learn as much as I can from a wide breadth of topics from automation to mobile platforms, from gaming technologies to application security.

If there is one thing I wish to impart, that that is this "Always respect your craft, your tests and your QA"

Comments and Discussions

 
QuestionPart 2, 3, etc...? Pin
Jeff Bowman19-Mar-19 12:59
professionalJeff Bowman19-Mar-19 12:59 
AnswerRe: Part 2, 3, etc...? Pin
Vlad Neculai Vizitiu25-Jun-19 0:27
Vlad Neculai Vizitiu25-Jun-19 0:27 
GeneralRe: Part 2, 3, etc...? Pin
Jeff Bowman25-Jun-19 9:11
professionalJeff Bowman25-Jun-19 9:11 
GeneralRe: Part 2, 3, etc...? Pin
Vlad Neculai Vizitiu26-Jun-19 19:20
Vlad Neculai Vizitiu26-Jun-19 19:20 
GeneralRe: Part 2, 3, etc...? Pin
Jeff Bowman27-Jun-19 7:06
professionalJeff Bowman27-Jun-19 7:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.