In this post, we will look at extensibility framework provided by Microsoft, that is “MEF(Managed Extensibility Framework)”. MEF provides great framework to dynamically add or remove functionalities. In this demonstration, we will create a simple tax calculation application. There are various kinds of tax calculations. Applicability of these taxes is country dependent. We will use MEF to add more and more tax calculations, and our main code doesn’t have to be altered to accommodate the changes.
We will look at some of the best practices also along the way to use MEF into your solution. We will be using Visual Studio 2012 for this demonstration; however the example will work for most of the visual studio versions.
Step 1: Create a blank solution in visual studio 2012. Let’s name the solution as “CalculationSelection”.
Step 2: Add a C# class library project to the solution. Let’s name the project as “CalculationInterface”. Here, I am trying to separate the entire contract thing into one project.
Step 3: Add an interface to this project named as “IMyCalculation”.
Step 4: Add the reference to System.ComponentModel.Composition. This will provide all the MEF functionalities.
Step 5: We will add one property and a method to this interface. C# code for IMyCalculation interface is shown below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CalculationContract
{
public interface IMyCalculation
{
int Input { get; set; }
void Calculate();
}
}
Here the Input is an integer variable, to take the salary input. The Calculate method will do all the calculation.
Step 6: Add a new interface “IMyCalculationAttribute.cs” to “CalculationInterface” project. The C# code for this class is as shown below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CalculationContract
{
public interface IMyCalculationAttribute
{
string[] Countries { get; }
}
}
Step 7: Add a new class MyCalculationAttribute.cs to “CalculationInterface” project. This class will be derived with the base class of “Attribute” and it will implement the previously created “CalculationInterface”.
This class will provide the implementation of custom ExportMetaData attribute feature.
C# code for this class is as shown below.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CalculationContract
{
[MetadataAttribute]
public class MyCalculationAttribute : Attribute, IMyCalculationAttribute
{
public MyCalculationAttribute(string[] countries)
{
Countries = countries;
}
public string[] Countries
{
get; set;
}
}
}
Now our contract is ready. We will create another class now, which will provide various tax calculations and export them using MEF.
Step 8: Add a C# class library project and name it as “AllCalculations”. Now we will add three tax calculations. First of all add the reference to “CalculationContract” project as well as to the System.ComponentModel.Composition.
Add first class named as “SalesTaxCalculation.cs” to this project. C# Code for this class is shown below:
using CalculationContract;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AllCalculations
{
[Export(typeof(IMyCalculation))]
[MyCalculationAttribute(new string[] { "AU", "FR", "CH" })]
public class SalesTaxCalculation : IMyCalculation
{
public int Input
{
get;
set;
}
public void Calculate()
{
Console.WriteLine("Sales Tax Calculation with input {0}", Input);
}
}
}
Similarly we will add two more classes to this project. C #codes for both classes are shown below.
EducationTaxCalculation.cs
using CalculationContract;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AllCalculations
{
[Export(typeof(IMyCalculation))]
[MyCalculationAttribute(new string[] {"IN", "US", "CH"}) ]
class EducationTaxCalculation : IMyCalculation
{
public int Input
{
get;
set;
}
public void Calculate()
{
Console.WriteLine("Education Tax Calculation using Salary {0}", Input);
}
}
}
CessCalculation.cs
using CalculationContract;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AllCalculations
{
[Export(typeof(IMyCalculation))]
[MyCalculationAttribute(new string[] { "IN", "FR", "CH" })]
public class CessCalculation : IMyCalculation
{
public int Input
{
get;
set;
}
public void Calculate()
{
Console.WriteLine("Cess Calculation with input {0}", Input);
}
}
}
Here you can see that in all these three calculations, I have implemented the IMyCalculation interface. Calculate method is nothing but just a console output.
We have marked each classes as [Export(typeof (IMyCalculation)] so that it can be exported and later on consumed by Imports. You can also see the usage of our previously created custom export Meta data attribute. The attribute constructor takes an array of string as a parameter. The array is list of countries where these taxes are applicable.
Now we have our exports ready, we will consume these into our console application.
Step 9: Add a new C# console application project to the solution and name it as “CalculationSelection”. C# code for the Program.cs is shown below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
using System.Reflection;
using CalculationContract;
namespace CalculationSelection
{
public class MyCalculations
{
//Constructor
public MyCalculations(int salary,string country)
{
Salary = salary;
Country = country;
}
public int Salary { get; set; }
public string Country { get; set; }
//ImportMany of type IMyCalculation
[ImportMany(typeof(IMyCalculation))]
public IEnumerable<Lazy<IMyCalculation,IMyCalculationAttribute>> AllCalculations;
}
class Program
{
static void Main(string[] args)
{
//Loading the assembly
var asm = Assembly.LoadFrom("AllCalculations.dll");
//Create the catalog
var catalog = new AssemblyCatalog(asm);
//Create the composition container
var container = new CompositionContainer(catalog);
//Create an instance of MyCalcuations class
var myCalculations1 = new MyCalculations(1000,"FR");
//Perform all the tax calculations
PeformCalculations(container, myCalculations1);
//Create another instance of MyCalcuations class
var myCalculations2 = new MyCalculations(5000, "IN");
//Perform all the tax calculations
PeformCalculations(container, myCalculations2);
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
static void PeformCalculations(CompositionContainer container, MyCalculations currentCalculation)
{
//Compose the parts, match the imports with exports
container.ComposeParts(currentCalculation);
//print the current country
Console.WriteLine("Current Country :{0}", currentCalculation.Country);
//loop through all available calculations inside the assembly
foreach (var item in currentCalculation.AllCalculations)
{
//retrieve the list of applicable countries from the metadata
var applicableCountries = item.Metadata.Countries;
//Check whether the current tax calculation is applicable in the selected country
var cmp = applicableCountries.Contains(currentCalculation.Country);
if (cmp)
{
//get the the value for lazy initialization
IMyCalculation calc = item.Value as IMyCalculation;
//provide the input salary
calc.Input = currentCalculation.Salary;
//perform the calculation
calc.Calculate();
}
}
}
}
}
Here is the brief explanation of code parts.
//ImportMany of type IMyCalculation [ImportMany(typeof(IMyCalculation))] public IEnumerable<Lazy<IMyCalculation,IMyCalculationAttribute>> AllCalculations;
Here we have used the Lazy<T,Metadata> as the type of import. Lazy loaders help in loading only the required calculation into the memory thus improving upon the performance.
//Loading the assembly
var asm = Assembly.LoadFrom("AllCalculations.dll");
Make sure you have added the reference to “AllCalculations” project. Alternatively, you could have saved the dll to some folder and passed the dll file path to load the assembly.
//Create the catalog var catalog = new AssemblyCatalog(asm); //Create the composition container var container = new CompositionContainer(catalog);
Then we are creating the Assembly catalog and the composition container. Then we are creating two instances of MyCalculations. For each the salary input and country value is different. When PeformCalcuation method is called, we are passing the container as well the MyCalculations class instance to the method.
//Compose the parts, match the imports with exports container.ComposeParts(currentCalculation);
Inside the PerformCalculation method , we are composing all the parts for current class instance. This method will match all the exports with imports.
//loop through all available calculations inside the assembly
foreach (var item in currentCalculation.AllCalculations)
{
//retrieve the list of applicable countries from the metadata
var applicableCountries = item.Metadata.Countries;
//Check whether the current tax calculation is applicable in the selected country
var cmp = applicableCountries.Contains(currentCalculation.Country);
if (cmp)
{
//get the the value from lazy loader
IMyCalculation calc = item.Value as IMyCalculation;
//provide the input salary
calc.Input = currentCalculation.Salary;
//perform the calculation
calc.Calculate();
}
}
Then we are iterating through all calculation available. We are getting the Metadata information from item.Metadata property. Then we are checking whether the current tax calculation is applicable in that country or not. If it is, then we are getting the item.Value to get the instance of type IMyCalculation. Once we have calculation instance with us, we can set the Input property and then invoke the Calculate method.
The final output is shown below.
Thus we are utilizing the custom export metadata to select the required extension or feature to be used.
The source code can be downloaded here.



