This week I will be one of the speakers at BASTA On Tour in Munich. One of the topics I am going to speak about is the Managed Extensibility Framework (MEF). In this blog post I want to share my slides and summarize the hands-on labs that I am going to go through with the participants.
Hands-On Lab 1: Directory Catalog Sample
Prerequisites:
Lab step by step description:
- Create a new command line project called DirectoryCatalogDemo.
- The application will ask the user for a string, apply a set of string operations on that string and output the result. The string operations should be extensible. A user should be able to copy an assembly into the program's directory and the application should automatically pick up the assembly and apply all operations that it contains. Therefore the first thing we need is a contract that all string operation parts have to implement.
- Add a new class library project called OperatorContract to your solution.
- Add the following interface to the newly created project:
namespace OperatorContract
{
public interface IStringOperator
{
string PerformOperation(string input);
}
}
- Now add an implemenation of
IStringOperator to the command line project
DirectoryCatalogDemo.
- Add a reference to System.ComponentModel.Composition to the command line project DirectoryCatalogDemo.
- Add the following class to the project:
using System.ComponentModel.Composition;
using OperatorContract;
namespace DirectoryCatalogDemo
{
[Export(typeof(IStringOperator))]
public class UppercaseOperator : IStringOperator
{
public string PerformOperation(string input)
{
return input.ToUpper();
}
}
}
- Next you have to implement the Program class as follows (note especially how MEF is used to load operators into the member Program.operators using the ComposeParts method):
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using OperatorContract;
namespace DirectoryCatalogDemo
{
public class Program
{
[ImportMany(AllowRecomposition = true)]
private IStringOperator[] operators;
static void Main(string[] args)
{
new Program().Run();
}
void Run()
{
DirectoryCatalog directoryCatalog;
var container = new CompositionContainer(
new AggregateCatalog(
new AssemblyCatalog(typeof(Program).Assembly),
directoryCatalog = new DirectoryCatalog(".")
));
container.ComposeParts(this);
var userInput = string.Empty;
do
{
Console.Write("Please enter a string (quit to exit program): ");
userInput = Console.ReadLine();
if (userInput != "quit")
{
directoryCatalog.Refresh();
Console.WriteLine(this.operators.Aggregate<IStringOperator, string>(userInput, (agg, op) => op.PerformOperation(agg)));
}
}
while (userInput != "quit");
}
}
}
- Build and run your program. Currently the program will only find the UppercaseOperator part because no other implementations of IStringOperator can be found. However, the application would already load additional operators from assemblies that are present in the application's directory. Next we will create a new operator and add it by just copying the assembly.
- Create a new class library project
ReverseStringOperator.
- Add a reference to System.ComponentModel.Composition to the class library.
- Add the following class to the project:
using System.ComponentModel.Composition;
using System.Linq;
using OperatorContract;
namespace ReverseStringOperator
{
[Export(typeof(IStringOperator))]
public class ReverseStringOperator : IStringOperator
{
public string PerformOperation(string input)
{
return new string(input.Reverse().ToArray());
}
}
}
- Build your solution.
- Run the command line application. Try it - the behavior must not have changed because the new operator is unknown by now.
- Copy the assembly with the ReverseStringOperator into the program's directory without stopping the application. Try it - the string must be turned to uppercase and be reversed now.
- Use the debugger to see how MEF loads the assembly during runtime.
This example shows you some basic principles of MEF. However, it could have been implemented much simpler because MEF is not only able to export and import objects. You can use the export/import logic for functions, too. Our operators are just functional (technically func<string, string>). Let us see how we could have made life easier by exporting and importing functions instead of objects:
- Add the following class to your command line project
DirectoryCatalogDemo:
- Note the Export attributes on the methods instead of the classes.
using System.ComponentModel.Composition;
using System.Linq;
namespace DirectoryCatalogDemo
{
public static class FuncationalOperators
{
[Export("FuncationOperator")]
public static string UppercaseString(string input)
{
return input.ToUpper();
}
[Export("FuncationOperator")]
public static string ReverseString(string input)
{
return new string(input.Reverse().ToArray());
}
}
}
- Change the implementation of the Program class as follows (new/changed lines are written in italic):
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using OperatorContract;
namespace DirectoryCatalogDemo
{
public class Program
{
[ImportMany(AllowRecomposition = true)]
private IStringOperator[] operators;
[ImportMany("FuncationOperator", AllowRecomposition = true)]
private Func<string, string>[] funcationalOperators;
static void Main(string[] args)
{
new Program().Run();
}
void Run()
{
DirectoryCatalog directoryCatalog;
var container = new CompositionContainer(
new AggregateCatalog(
new AssemblyCatalog(typeof(Program).Assembly),
directoryCatalog = new DirectoryCatalog(".")
));
container.ComposeParts(this);
var userInput = string.Empty;
do
{
Console.Write("Please enter a string (quit to exit program): ");
userInput = Console.ReadLine();
if (userInput != "quit")
{
directoryCatalog.Refresh();
Console.WriteLine("Operators from classes: {0}",
this.operators.Aggregate<IStringOperator, string>(userInput, (agg, op) => op.PerformOperation(agg)));
Console.WriteLine("Funcational operators: {0}",
this.funcationalOperators.Aggregate<Func<string, string>, string>(userInput, (agg, op) => op(agg)));
}
}
while (userInput != "quit");
}
}
}
- Compile and test the application. As you can see MEF correctly exports and imports the methods just as expected.
Hands-On Lab 2: Part Lifecycle Sample
Prerequisites:
Lab step by step description:
- Create a new command line project called LifeCycleDemo.
- Add an exported class to the project as follows:
[Export]
public class DisposableObject : IDisposable
{
private bool disposed = false;
public DisposableObject()
{
Trace.WriteLine(string.Format("Creating object {0}", this.GetHashCode()));
}
public void Dispose()
{
Trace.WriteLine(string.Format("Disposing object {0}", this.GetHashCode()));
this.disposed = true;
}
public void DoSomething()
{
if (this.disposed)
{
throw new InvalidOperationException();
}
// Here we have to do something with this object
}
}
- Change the implementation of the Program class as follows:
public class Program : IPartImportsSatisfiedNotification
{
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public Lazy<DisposableObject> mefObject1;
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public Lazy<DisposableObject> mefObject2;
static void Main(string[] args)
{
new Program().Run();
}
private void Run()
{
var catalog = new AssemblyCatalog(typeof(DisposableObject).Assembly);
var container = new CompositionContainer(catalog);
Trace.WriteLine("Start composing");
container.ComposeParts(this);
Trace.WriteLine("Accessing lazy object");
this.mefObject1.Value.DoSomething();
this.mefObject2.Value.DoSomething();
container.ReleaseExports(new[] { this.mefObject1, this.mefObject2 });
Trace.WriteLine("Start composing");
container.ComposeParts(this);
Trace.WriteLine("Accessing lazy object again");
this.mefObject1.Value.DoSomething();
this.mefObject2.Value.DoSomething();
}
public void OnImportsSatisfied()
{
Trace.WriteLine("Imports are satisfied");
}
}
- Build your project.
- Run your project in the debugger and try to reconstruct the following important points:
- Lazy parts are constructed at first access.
- ReleaseExports disposes all parts (if they implement IDisposable)
- When is OnImportsSatisfied called?
- What happens if you change the code so that you explicitly dispose the container object?
Hands-On Lab 3: Using MEF To Extend A WPF Application
Prerequisites:
- Visual Studio 2010
- Download and install the latest version of the Visual Studio 2010 and .NET Framework 4 Training Kit
- All the requisites for this lab are verified using the Configuration Wizard. To make sure that everything is correctly configured, follow these steps.
- Note: To perform the setup steps you need to run the scripts in a command window with administrator privileges.
- Run the Configuration Wizard for the Lab if you have not done it previously. To do this, run the
CheckDependencies.cmd script located under the
Source\Setupfolder of this lab. Install any pre-requisites that are missing (rescanning if necessary) and complete the wizard.
- Note: At the end the wizard will try to install code snippets to complete the lab. You need not install the snippets if you do not want to. Just cancel the wizard at this step to prevent installing software to Visual Studio 2010.
Lab step by step description:
- Open Labs.htm in your favorite browser (IE recommended). You find this file in the installation folder into which you have installed the training kit (see prerequisites above).
- Choose Extensibility in the menu on the left side.
- Click on Launch Lab in the middle column.
- Complete extercises 1 and 2 of the lab.
Hands-On Lab 4: MEF And Silverlight
Prerequisites:
Lab step by step description:
- Open Labs.htm in your favorite browser (IE recommended). You find this file in the installation folder into which you have installed the training kit (see prerequisites above).
- Choose Extensibility in the menu on the left side.
- Click on Launch Lab in the middle column.
- Complete extercises 3 of the lab.
comments powered by