Developing modular applications with MEF

Introduction

When we create LOB (Line-of-business) applications, we use
to develop them in a modular way, to satisfy many requisites:

  • The application is sold by modules – the
    customer can buy the sales module, but not the industrial one.
  • The application can be developed by separated
    teams – each team develops an independent module.
  • The modules are only loaded when the user needs
    it.
  • The application doesn’t need to be completely
    redeployed when a module changes
  • We can create new modules and add them to the
    application with no need to change the other modules.

To create a modular application we must create a custom
infrastructure that needs lots of work and can introduce bugs to the system

.Net 4.0 brought a new resource that allows us to create
modular and extensible applications: MEF (Managed Extensibility Framework).
This is a framework to create extensible apps and it is thoroughly tested – Visual
Studio uses MEF for its extension system: when you use a Visual Studio add-in,
you are using MEF.

Introduction to MEF

Before developing out modular application, we will see how
to use MEF. To use it, you must include a reference to System.ComponentModel.Composition. After adding the reference we
must tell to the program which are the parts that must be combined: on one
side, we have the exported classes, the modules that will be added to the main
module, that will fit in the imported parts.

To do this association and the module discovery, combining
the exported parts to the imported ones we use Containers. They will discover the modules and will combine the
exported to the imported parts. Containers can use catalogs to find the
application parts. Does this seem difficult? Let’s see how does it work in the
real world.

Create a new console project and add a reference to System.ComponentModel.Composition.
Create a new class and call it Menu:

public class Menu
{
    private Module _module;
    public void OptionList()
    {
       Console.WriteLine(_module.Title);
    }
}

We need to create the Module
class:

public class Module
{
    public Module()
    {
        Title = "Customers";
    }
    public string Title { get; set; }
}

Now we need to declare what will be exported and where this
exported class will fit, using the [Import]
and [Export] attributes:

public class Menu
{
    [Import]
    private Module _module;

    public void OptionList()
    {
       Console.WriteLine(_module.Title);
    }
}

[Export]
public class Module
{
    public Module()
    {
        Title = "Customers";
    }
    public string Title { get; set; }
}

Note that the _module
field in the Menu class is private –
this doesn’t affect the composition. Now, we need to create our container and
let it do its magic. In Program.cs
put the following code:

static void Main(string[] args)
{
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);
    var menu = new Menu();
    container.ComposeParts(menu);
    menu.OptionList();
    Console.ReadLine();
}

We have created an AssemblyCatalog
(that searches the parts in the assembly – in our case, in the current
assembly), then created the container that composes the parts after the menu is
created. When the menu.OptionList()
method is called, the module’s title is listed:

Getting more than one component

Now, you will say: but we have only one module listed. How
do we do to have more than one? We must create a new interface IModule:

public interface IModule
{
    string Title { get; set; }
}

Our class will implement this interface and the export will
tell that we are exporting the IModule
interface:

[Export(typeof(IModule))]
public class Module : IModule
{
    public Module()
    {
        Title = "Customers";
    }

    public string Title { get; set; }
}

In order to match the parts, we must say that the imported
part is also of IModule type:

public class Menu
{
    [Import]
    private IModule _module;
    public void OptionList()
    {
       Console.WriteLine(_module.Title);
    }
}

We execute the application and see that everything works
like before. We can add the new modules:

[Export(typeof(IModule))]
public class Customer : IModule
{
    public Customer()
    {
        Title = "Customers";
    }
    public string Title { get; set; }
}

[Export(typeof(IModule))]
public class Product : IModule
{
    public Product()
    {
        Title = "Products";
    }
    public string Title { get; set; }
}

[Export(typeof(IModule))]
public class Supplier : IModule
{
    public Supplier()
    {
        Title = "Suppliers";
    }
    public string Title { get; set; }
}

We execute the application and… we get a ChangeRejectedException exception:
More than one export was found that
matches the constraint: ContractName IntroMEF.IModule
“. MEF is
complaining that we have many classes that export IModule.

The [Import]
attribute allows only one export for the same interface. If there is more than
one, this exception is thrown. When we want that many classes export the same
interface, we must use the [ImportMany]
attribute. To allow importing all the classes, we need to change the import a
little, changing the attribute to [ImportMany]
and the property type of the _module
field to IEnumerable<IModule>:

public class Menu
{
    [ImportMany]
    private IEnumerable<IModule> _modules;

    public void OptionList()
    {
        foreach (var module in _modules)
        {
           Console.WriteLine(module.Title);
        }
    }
}

We also changed OptionList
to list all the modules. Now, when we execute the application, all the modules
are found and listed:

Working with modules in different assemblies

You may be thinking: “this is easy to do – everything is in
the same assembly. Doesn’t MEF allow modular and extensible apps?”. All the
magic is in the container and in the catalog. We have created an AssemblyCatalog pointing to the current
assembly, but we could do the same thing pointing to another assembly. You will
answer: “this is still easy, I can add a reference to the other assembly where
the modules are located. Where is the magic?”.

AssemblyCatalog
is not the only catalog that we can use. We can use other catalog types, like
the DirectoryCatalog, that finds
parts in assemblies located in a specified folder.

Let’s change our project: in the solution, create a Portable
Class Library and give it the name of IntroMEF.Interfaces.
Choose the desired frameworks (use Framework 4.0 or higher) and delete the Class1.cs file. Add a new interface and
put there the IModule interface,
removing it from the main project. On the main project, add a reference to the
interface project.

Create a new Class library project and give it the name of IntroMef.Modules. Add a reference to System.ComponentModel.Composition and
remove the Class1.cs file. Add a
reference to IntroMef.Interfaces and
move the classes Customer, Product and Supplier to the new project.

Now we need to tell that our parts are not only in the
current assembly, but they can also be found in the current folder. For that we
must compose two catalogs: one for the current assembly (for the imported
parts) and another for the folder (for the exported parts). We will use an AggregateCatalog to compose both
catalogs:

static void Main(string[] args)
{
var catalog = new AggregateCatalog(
new AssemblyCatalog(Assembly.GetExecutingAssembly()),
new DirectoryCatalog(“.”));
var container = new CompositionContainer(catalog);
var menu = new Menu();
container.ComposeParts(menu);
menu.OptionList();
Console.ReadLine();
}

We execute our application and nothing happens – no module
is listed. That is due to the fact that we haven’t copied the modules assembly
to the folder where the executable is located. Copy the assembly IntroMef.Modules to the executable
folder, open a command window and execute the application again. Now the
modules are recognized.

We didn’t need to add any references to the main project in
the module library nor add a reference to the library in the main project. All
we had to do is to copy the file to the executable folder. MEF found the
modules when the assembly file was in the correct folder.

Creating a WPF project with MEF

Now you may be thinking: “that’s great, but I don’t create
any console projects – how do I do a modular project using WPF?”. The answer
is, in the same way we create a console application. To show that I am not
lying, we will create a WPF project.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="120" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <ListBox Grid.Column="0" ItemsSource="{Binding Modules}" 
             DisplayMemberPath="Title" x:Name="LbxMenu"/>
    <ContentControl Grid.Column="1" Content="{Binding ElementName=LbxMenu, 
        Path=SelectedItem.Content}" />
</Grid>

We are adding a grid with two columns. In the first one we
will add the found modules and in the second one, the contents for the selected
module.  To make it work, we must have
two properties in every module, Title,
with the module’s title and Content,
a UserControl with the contents of
that module.

We need to create an interface for the modules. We will do
in the same way we did before, creating a new library. Create a class library
and name it WPFMef.Interfaces. Add
the references to PresentationCore
and WindowsBase. Remove the Class1.cs file and add a new interface IModule:

public interface IModule
{
    string Title { get; }
    UIElement Content { get; } 
}

Then create a new class library for the modules. Add a new
class library and name it WPFMef.Modules.
Add the references to WPFMef.Interfaces,
System.ComponentModel.Composition, System.Xaml, PresentationCore, PresentationFramework
and WindowsBase. Create the exported
classes:

[Export(typeof(IModule))]
public class Customer : IModule
{
    public string Title
    {
        get { return "Customers"; }
    }

    public UIElement Content
    {
        get { return new CustomerList(); }
    }
}

[Export(typeof(IModule))]
public class Product : IModule
{
    public string Title
    {
        get { return "Products"; }
    }

    public UIElement Content
    {
        get { return new ProductList(); }
    }
}

[Export(typeof(IModule))]
public class Supplier : IModule
{
    public string Title
    {
        get { return "Suppliers"; }
    }

    public UIElement Content
    {
        get { return new SupplierList(); }
    }
}

The next step is to create the views that will be shown when
the option is selected. Create three UserControls with a content similar to
this one:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <TextBlock Text="Customer List" HorizontalAlignment="Center" 
               VerticalAlignment="Center" FontSize="18"/>
    <ListBox x:Name="List" Grid.Row="1"/>
</Grid>

On the constructor of the view, add this code to add items
to the ListBox:

public CustomerList()
{
    InitializeComponent();
    List.ItemsSource = Enumerable.Range(1, 100)
        .Select(n => "Customer " + n);
}

We have added a simple window that shows “Customer 1″ to
“Customer 100″, just to show what can be done. Our module library is finished.

Go back to the main project, add the references to the
library WPFMef.Interfaces and to System.ComponentModel.Composition.
Create a new class and give it the name of MainViewModel.
We will add there the code to initialize the container and the Modules property:

public class MainViewModel
{
    [ImportMany]
    public IEnumerable<IModule> Modulos { get; set; }

    public MainViewModel()
    {
        var catalog = new AggregateCatalog(
            new AssemblyCatalog(Assembly.GetExecutingAssembly()),
            new DirectoryCatalog("."));
        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }
}

We only need to associate the ViewModel to the View. In
MainWindow.xaml.cs, add the following code:

public MainWindow()
{
    InitializeComponent();
    DataContext = new MainViewModel();
}

The project is ready and can be executed. When we execute
it, we don’t see anything in the window. We forgot to copy the assembly with
the modules again. Just copy the assembly to the executable folder and run the
program again. Yesssss!

The data is shown, when we select an option the content view
at the right is changed.

If you don’t want to copy the project manually, you can
change the project options, changing the output path for the modules, pointing
to the executable path:

 

Conclusions

As you can see, MEF allows creating modular applications in
an easy way that allows expanding our projects just by copying the dlls with
the new modules. That works the same way in Windows Forms, WPF, Silverlight,
Asp.Net and even Windows 8 (using NuGet). You have a standard way to create plug-ins
that are not coupled, allow development done by many teams and adding or fixing
modules with no need to recompile the whole project.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>