Saturday, April 9, 2011

Introducing convention based programming for Managed Extensibility Framework

More than a year ago my friend @TheCodeJunkie wrote a very nice addition to Managed Extensibility Framework – the ConventionCatalog. Since then it’s a part of MefContrib project. However, I haven’t seen many people using it. My guess is that there is no much information about it, besides this post. So in this post I will discuss what a convention model is, and how to use it!

What is ConventionCatalog ?

Convention model (aka. ConventionCatalog) allows you to specify conventions to produce parts, exports and imports. Okay, this is all fine, but give me an example! Here is a convention you can define: For every type in assembly MyCompany.MyFantasticProgram, if a type implements IWidget, please export it with contract type IWidget and import property named ViewModel. Nice, huh? Say you have Widget1 and Widget2 classes implementing IWidget interface. image They will be automatically exported, and moreover, they don’t have to be attributed using standard MEF attributes!!! This is because the ConventionCatalog defines an extensible mechanism for writing such conventions! And you can write your own easily! One thing to note about the above convention – it will be applied to many types, particularly all implementing the IWidget interface. This is important as you don’t have to explicitly export all the classes individually. At the same time, it is perfectly legal to apply a convention to a single type. Example? If you ever encounter Widget123 type, please export it using IWidget contract type and add Location metadata with a value of Location.Left (enumeration). Here, only a single class (part) will be affected – the Widget123 class. Now you have a pretty good understanding what conventions are in the beautiful world of MEF, but how can I specify them in a program ? So as I stated previously, the catalog is extensible, so.. any possible way of specifying the conventions is… possible! Originally, @TheCodeJunkie developed a pretty nice fluent interface for defining them (as part of MefContrib 1.1 I’ve added some extension methods to ease the use of fi). Here is a code which defines the initial convention (it does not specify where to look for the types to apply it – be patient)

Part()
.ForTypesAssignableFrom<IWidget>()
.ExportAs<IWidget>()
.ImportProperty("ViewModel");


Recently, I have developed a registration mechanism which enables to specify parts via the configuration file – App.config. Next section outlines both methods using a simple use case.



How can I use it ?



Let’s begin with a simple scenario. Let’s build a console application which can display a list of movies from various data sources. Here are the main classes and interfaces involved in the design: MovieClasses The core interface is IMovieProvider which provides an abstraction over any movie repository and has only one method returning a list of movies from a single movie source. It also has two dummy implementations – nothing interesting. This interface is consumed by the MovieLister class which implements IMovieLister and acts as a facade on top of the providers. It simply returns all movies from all providers available, with optional filtering by movie title. MovieLister also performs some simple logging, and the logger is injected using the constructor injection. The complete code for that class is right here:



public class MovieLister : IMovieLister
{
private readonly ILogger _logger;

public MovieLister(ILogger logger)
{
_logger = logger;
}

public IMovieProvider[] Providers { get; set; }

public IEnumerable<Movie> GetMovies()
{
var movies = Providers
.SelectMany(movieProvider => movieProvider.GetMovies())
.ToList();

_logger.Log(string.Format("Loaded {0} movies.", movies.Count));

return movies;
}

public IEnumerable<Movie> GetMoviesByName(string name)
{
var movies = GetMovies()
.Where(m => m.Name.Contains(name))
.ToList();

_logger.Log(string.Format("Found {0} movies matching '{1}'.", movies.Count, name));

return movies;
}
}

Note that there is no sign of standard MEF Export/Import attributes, and the constructor is not attributed with ImportingConstructor! Finally, there is a Program class which wraps the app. Here’s the code:

public class Program
{
[Import]
public IMovieLister MovieLister { get; set; }

public static void Main(string[] args)
{
var p = new Program();
p.Init();
p.Run();
}

private void Init()
{
var conventionCatalog = new ConventionCatalog(new MoviePartRegistry());
var container = new CompositionContainer(conventionCatalog);

// Composing this part will inject MovieLister property
container.ComposeParts(this);
}

private void Run()
{
var movies = MovieLister.GetMoviesByName("Movie");
foreach (var movie in movies)
{
Console.WriteLine(movie.Name);
}
}
}

No doubt the most interesting part in the above code is the Init() method. I create there the ConventionCatalog (note this is the only catalog used in this example) and pass an instance of some registry. And here we came to the very important thing: all conventions are defined (or I should rather say can be understood) thanks to the IPartRegistry interface. So to introduce a custom way of defining  conventions, you have to implement that interface (if you want to understand some internals of how ConventionCatalog works, read this post). By default, MefContrib 1.1 ships with two implementations:


  • PartRegistry – this class provides fluent interface to register part/export/import conventions,


  • ConfigurationPartRegistry – this class provide the XML way of specifying conventions.


Okay, so let’s see what it takes to register our parts using fluent interface.

public class MoviePartRegistry : PartRegistry
{
public MoviePartRegistry()
{
// Apply the conventions to all types int the specified assembly
Scan(c => c.Assembly(typeof(Program).Assembly));

Part<MovieLister>()
.MakeShared() // make this part shared
.ExportAs<IMovieLister>() // and export it with contract type IMovieLister
.ImportConstructor() // use constructor injection
.Imports(x =>
{
x.Import<MovieLister>() // import on part MovieLister
.Member(m => m.Providers) // member named 'Providers'
.ContractType<IMovieProvider>(); // with contract type IMovieProvider
});

Part()
.ForTypesAssignableFrom<IMovieProvider>()
.ExportAs<IMovieProvider>();

Part<LoggerImpl>()
.MakeShared()
.ExportAs<ILogger>();
}
}

I derive from PartRegistry (although this is not a requirement, I prefer to do this that way) and do two things there. First, I specify to which types I want to apply my conventions. This is done through a nice lambda expression. What it does under the hoods ? It creates appropriate type scanners. A type scanner (anything implementing ITypeScanner interface) is responsible for returning all the types for which the user wants to assign conventions, if there are any applicable). Out of the box, there are:


  • TypeScanner, which can return user specified types,


  • AssemblyTypeScanner – returns all the types from given assembly,


  • DirectoryTypeScanner – returns all the types from all assemblies from a given disk folder,


  • AggregateTypeScanner – aggregates types from various type scanners.


Does it resemble you something ? Yes! Catalogs from MEF of course! This is because MEF catalogs are responsible for pulling parts/exports/imports from types. Similarly, we obtain types for which we want to apply the conventions. Okay, that’s all in terms of how to specify types for conventions. Lets move on to how to actually specify a convention. There’s a single method called Part() which returns PartConventionBuilder which has a nice fluent API to define part conventions. Part() method comes in two flavours – generic and nongeneric. The difference is that the generic Part<PartType>() method creates a convention in a strongly typed manner and applies that convention only to the part type specified as a generic type argument. However, if you want to leverage strong typing and specify conventions for other types as well, you can force it using ForTypesAssignableFrom() method. Actually, to specify types for which you want to apply the convention, you can use any of the For*() methods. It would be pointless to further explain the API, please play with it and let us know how does it feel – we can always improve! You probably have noticed how nicely IMoveProvider instances are exported. But what if we wanted to be able to specify which providers we want to use ? Of course there are many ways of achieving this, including leveraging metadata facilities of MEF. One method could also be to specify movie providers in the App.config file rather than exporting all IMovieProvider implementations using a convention. Let’s explore that possibility.


To configure parts using XML, we need to add another part registry to convention catalog. Change the Init() method to this:

private void Init()
{
var conventionCatalog = new ConventionCatalog(
new ConfigurationPartRegistry("mef.configuration"),
new MoviePartRegistry());
var container = new CompositionContainer(conventionCatalog);

// Composing this part will inject MovieLister property
container.ComposeParts(this);
}

ConfigurationPartRegistry class accepts a string representing the name of the config section used to configure parts. Next, remove the fluent registration for IMovieProviders. Finally, add the App.config with the following content:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section
name="mef.configuration"
type="MefContrib.Hosting.Conventions.Configuration.Section.ConventionConfigurationSection, MefContrib" />
</configSections>

<mef.configuration>
<parts>

<part type="ConventionCatalogDemo.MovieProvider1, ConventionCatalogDemo" creationPolicy="Shared">
<exports>
<export contractType="ConventionCatalogDemo.IMovieProvider, ConventionCatalogDemo" />
</exports>
</part>

<part type="ConventionCatalogDemo.MovieProvider2, ConventionCatalogDemo" creationPolicy="Shared">
<exports>
<export contractType="ConventionCatalogDemo.IMovieProvider, ConventionCatalogDemo" />
</exports>
</part>

</parts>
</mef.configuration>

</configuration>

Well, what do we have in here ? :) We have a MEF configuration specifying a set of parts. Each part must have a full .NET type associated with it (type name + assembly). The part (not surprisingly) can have a set of exports it offers and of course a set of imports it consumes. Each export can specify:


  • contractType – type of the contract,


  • contractName – name under which the export will be available,


  • member – name of the member of the part to export (leave empty to export the whole part itself),


  • metadata – a collection of metadata-items specifying metadata that will be attached to the export.


Each import can specify:


  • contractType – type of the contract,


  • contractName – name used to resolve the import,


  • member – name of the member of the part to import,


  • creationPolicy – required creation policy,


  • allowDefault – whether the import allow default values,


  • isRecomposable – whether the import is recomposable,


  • required-metadata – a collection of metadata-items specifying required metadata.



To specify a set of imports for a part use imports xml element and fill it with import xml elements. I guess everything is self explanatory. If you have any doubts, pleas see MefContrib.Tests project to see some more examples.



Where can I find it ?



ConventionCatalog discussed in this post is available in MefContrib project. New ConfigurationPartRegistry ships as part of the MefCotrinb 1.1. Please download sources and binaries from mefcontrib.com. Sample code used in this post can be downloaded here. There is also another sample application (WPF based), which shows how ConventionCatalog can be used. You can download it from our official MefContrib-Samples Github repository. Here’s the screenshot for that app: ExtensibleDashboardOnConventions Enjoy and let me know if you like it!

2 comments:

maether said...

The whole idea behind MEF is great but it requires a different approach on planning software architecture that I'm used to (and it looks kinda daunting at first sight).

Anyway, thank you for yesterday's lecture at Geeks On Tour in Katowice :)

Łukasz Tyl

jimmy jam said...

This is because the ConventionCatalog defines an extensible mechanism for writing such conventions! And you can write your own easily! One thing to note about the above convention – it will be applied to many types, particularly all implementing the IWidget interface. tech blog