Willy’s Cave Dwelling Notes #10 – Managed Extensions Framework (MEF) … simplicity rules!
The Quick Response Sample – TFS Branch Scenarios sample solution is based on the Managed Extensions Framework (MEF). To understand MEF and be in a better position to review the code base, I decided to do a quick tour and build a simple calculator using MEF (we started calculator samples in the 90’s, so why stop now…).
WISH: Would it not be great to have a simple application and simply throw extensions (add, subtract) into the mix, without having to continuously update the engine room?
Here we go … calculator a la MEF!
Getting Started
Let’s start with a simple console application that takes two values and an operand … but is missing the calculation engine:
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.)
// This is sample code only, do not use in production environments
//
// Part of Visual Studio ALM Rangers - Willy's Cave Dwelling Journals
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.ALMRangers.Samples.CSharpFeatureTour.MEF
{
class Program
{
private Program() {}
static void Main(string[] args)
{
// Argument 1 = value 1
// Argument 2 = value 2
// Argument 3 = operand
if (3 != args.Length) Console.WriteLine("Rudimentary error handler: You must specifiy three " +
"arguments: value value operand, i.e. 1 1 +");
else
{
//Console.WriteLine("{0} {1} {2} = Not implemented", args[0], args[2], args[1]);
try
{
Program program = new Program();
long valueOne = Int64.Parse(args[0]);
long valueTwo = Int64.Parse(args[1]);
char symbol = args[2][0];
string result = "ERROR! Calculation extension not found";
// Display result
Console.WriteLine("{0} {1} {2} = {3}", args[0], args[2], args[1], result);
}
catch (Exception ex)
{
// Really rudimentary error
Console.WriteLine("Problem in Program Main when calling the extension: ", ex.ToString());
}
}
Console.ReadLine();
}
}
}
The next step would typically be to implement a monster switch, or possibly a factory using reflection and then perform calculations based on the third parameter (operand). But wait, there is MEF…
The 3-step implementation checklist:
- Define the contract
- Update the application code
- Implement extension(s)
1. Define the contract
We begin by defining a simple contract that all calculation extensions will implement, passing two long integer values and receiving back the calculated value.
We also add a contract to deal with the symbol and request that you park the question “but how do we know what to do with the two values? ” … watch the space.
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.)
// This is sample code only, do not use in production environments
//
// Part of Visual Studio ALM Rangers - Willy's Cave Dwelling Journals
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.ALMRangers.Samples.CSharpFeatureTour.MEF.Shared
{
// Calculation interface
public interface ICalculation
{
long Calculation(long valueOne, long valueTwo);
}
// Symbol interface
public interface ICalculationSymbol
{
char Symbol {get;}
}
}
2. Update the application code
We revise the consumer code as follows:
- Add the MEF namespaces (Comment #1)
- Define variables for the MEF container (Comment #2)
- Instantiate the catalog, container and import the extensions in the program constructor (#region Prepare MEF stuff )
- Add code which uses the extensions to perform the calculations (#region MEF Code)
Our program code now looks as follows:
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.)
// This is sample code only, do not use in production environments
//
// Part of Visual Studio ALM Rangers - Willy's Cave Dwelling Journals
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Comment #1: Add MEF namespaces
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
// Add our shared interfaces
using Microsoft.ALMRangers.Samples.CSharpFeatureTour.MEF.Shared;
namespace Microsoft.ALMRangers.Samples.CSharpFeatureTour.MEF
{
class Program
{
// Comment #2: Extension container
private CompositionContainer extensionContainer;
#region Prepare MEF stuff
// Import many potential calculators, with metadata
[ImportMany]
public IEnumerable<Lazy<ICalculation, ICalculationSymbol>> calculations = null;
private Program()
{
// MEF catalog that can combine (aggregate) multiple catalogs
var extensionCatalog = new AggregateCatalog();
// Add our calculation catalog, whereby we are keeping all extensions in our own
// assembly to keep this sample code simple. You can also (and probably should)
// look outside your own assembly using the DirectoryCatalog
extensionCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
// Create the container using our catalog
extensionContainer = new CompositionContainer(extensionCatalog);
// Fill the imports
try
{
this.extensionContainer.ComposeParts(this);
}
catch (Exception ex)
{
// Really rudimentary error
Console.WriteLine("Problem in the Program constructor: ", ex.ToString());
}
}
#endregion
static void Main(string[] args)
{
// Argument 1 = value 1
// Argument 2 = value 2
// Argument 3 = operand
if (3 != args.Length) Console.WriteLine("Rudimentary error handler: You must specifiy three " +
"arguments: value value operand, i.e. 1 1 +");
else
{
//Console.WriteLine("{0} {1} {2} = Not implemented", args[0], args[2], args[1]);
try
{
Program program = new Program();
long valueOne = Int64.Parse(args[0]);
long valueTwo = Int64.Parse(args[1]);
char symbol = args[2][0];
string result = "ERROR! Calculation extension not found";
#region MEF Code
foreach ( Lazy<ICalculation, ICalculationSymbol> calculator in program.calculations )
{
if (calculator.Metadata.Symbol.Equals(symbol))
{
result = calculator.Value.Calculation(valueOne, valueTwo).ToString();
break;
}
}
#endregion
// Display result
Console.WriteLine("{0} {1} {2} = {3}", args[0], args[2], args[1], result);
}
catch (Exception ex)
{
// Really rudimentary error
Console.WriteLine("Problem in Program Main when calling the extension: ", ex.ToString());
}
}
Console.ReadLine();
}
}
}
If we have the courage and run the code we get an error as expected … ‘cause we have not yet implemented any extension to satisfy the calculation symbol:
3. Implement extension(s)
To implement an extension is easy. We define the metadata (symbol) and implement the calculation interface.
The MEF’y code can be found in lines 14 (using …), 18 (export) and 19 (ExportMetadata):
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.)
// This is sample code only, do not use in production environments
//
// Part of Visual Studio ALM Rangers - Willy's Cave Dwelling Journals
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Add MEF namespaces
using System.ComponentModel.Composition;
namespace Microsoft.ALMRangers.Samples.CSharpFeatureTour.MEF.Shared
{
[Export(typeof(ICalculation))]
[ExportMetadata("Symbol",'+')]
class CalculationExtension_Add: ICalculation
{
public long Calculation(long valueOne, long valueTwo)
{
return valueOne + valueTwo;
}
}
}
We re-run the code now, we have a different result … even though we did not touch the main program code:
To add more extension simply create another class, define a unique symbol and implement the calculation.
Simplicity as we all love it!
In “real life” when we are not experimenting in sample code, we probably want to extract the MEF instantiation logic out of the main application code. Oh, what doe this application look like when we peek into it using the Architecture Explorer?
Solution View
Class View
I prefer this view, because it highlights the hubs and how the two hubs communicate via the Symbol property and the Calculation method. JMP and team, you have done a great job with these visualizations … much crisper and clearer
That is the end for today … now that we have gone through this simple exercise, we can download and review the Quick Response TFSBranchTool sample code and really enjoy the extensibility delivered by MEF.
You can also retrieve the latest Sample code created as part of the cave dwelling series from the Supporting Guidance and Whitepaper site.