Bringing convention based registration to the Managed Extensibility Framework
If you have used the Managed Extensibility Framework before, you know that is uses attributes such as ImportAttribute, ImportManyAttribute or ExportAttribute as part of its discovery mechanism. This approach to configuration is know as the attributed programming model and it is a very easy to pick up and start using. As a believer of the convention over configuration software design pattern, adding configuration attributes on my domain objects, attributes which serves no other purpose than that of discoverability, is something I would like to avoid doing.
As a result of this, I decided to create a convention based programming model that could be used instead of, or side-by-side with, the attributed programming model. The result of this is a custom catalog which relies on conventions to define that parts without the use of attributes on your objects.
By using conventions you also get the ability to take any available type and have it participate in composition, something you would not be able to when using the attributed model since it requires you to have access to the source code, in order to add the attributes. This opens up for some pretty interesting scenarios because you are able to take types, that as not designed with MEF in mind, and have them take part in composition.
If you want to try out the convention based programming model yourself, you can head over to the MEF Contrib website. At the time of this writing, the latest binary release of MEF Contrib is 0.9.0.0, which does not include the convention based programming model, so you will have to grab the latest version of the source code from the repository. It will definitely be in the next binary release though.
Before we get into the details of what the convention based programming model is, let me start with telling a bit about it it (currently is not). This release does not signal a final product, it should be considered more like a technology preview. The code has been put through quite a lot of testing, but chances are that there are still some bugs (if you find any, then please let me know so that I can resolve them) that have not been caught yet. It is also likely that there will be breaking changes in later releases, but I will do my best to avoid or minimize them.
The are a couple of limitations in the current implementation, such as not supporting constructor imports and not being able to compose existing instances of a type. However, I am aware of these short coming and they are all planned to be resolved in following releases. At the end of this post I will mention a couple of things on how you can help shape the future releases of the convention based programming model, but for now let us take a closer look on what makes it tick.
Convention Primitives
There are three interfaces in the model that defines the type of conventions that can be created; IPartConvention, IImportConvention and IExportConvention. All of the convention interfaces follow the same design. The conventions use a predicate to identify the things that they should be applied to, as well as a series of properties that define things such as contract name, type identity, creation policy, and metadata that will be applied to the created parts.
For each of the interfaces there is a default implementation. These implementations are, not surprisingly, called; PartConvention, ImportConvention and ExportConvention. Instances of these types are used to define the conventions that should be used by MEF. You can provide additional functionality by inheriting the convention types or provide your own by implementing the convention interfaces yourself.
Being able to create conventions with custom functionality is a very powerful feature in the convention based programming model and the rest of the model have been designed with this in mind by exposing extensibility points that will make it very easy to make their use as transparent as possible. So if you ever find yourself in a place where you are missing some functionality, just add it!
Convention Registries
Manually creating instances of the convention types can be tedious work, but thankfully there is a thing called convention registries that help provide a better experience. The registries are implementations of the Object Scoping pattern and provide a Domain-specific language (or DSL for short) for defining conventions. This pattern is used by popular frameworks, such as NHibernate and StructureMap.
The registries takes advantage of powerful language features such as nested closures, lambda expressions and extension methods to provide a richer setup experience then what you get when manually creating convention instances.
Below is a sample registry that is used to define two parts. The first part exports the ApplicationHost type as a singleton and also defines that the Extensions property of that type should be treated as an import with the contract name and type of IApplicationExtension. The second part exports all types that implement the IApplicationExtension interface, using the same contract name and type as that used for the Extensions property on the ApplicationHost type.
public class ExtensionRegistry : PartRegistry{ public ExtensionRegistry() { Part() .ForTypesMatching(x => x.Equals(typeof(ApplicationHost))) .MakeShared() .Exports(x => x.Export().Members(m => new[] { m })) .Imports(x => { x.Import() .Members(m => new[] { m.GetProperty("Extensions") }) .ContractName<IApplicationExtension>() .ContractType<IApplicationExtension>(); }); Part() .ForTypesMatching(x => x.GetInterfaces().Contains(typeof(IApplicationExtension))) .Exports(x => { x.Export() .Members(m => new[] { m }) .ContractName<IApplicationExtension>() .ContractType<IApplicationExtension>(); }); }}Do not worry if you think this looks complicated and verbose, you are right but fortunately what you are seeing is the raw registry DSL and there are several ways in which this could be made a lot simpler. Later in the post I will go into a bit more detail on how you can customize the registry DSL, but until then know that you are not stuck with the above code. The entire convention based programming model have been designed with extensibility in mind.
Type loader
Earlier I mentioned that all of the convention types used a predicate to determine that they can be applied to. For part conventions the predicate is on the Type type, meaning it examines an entire type to determine if the convention could be applied to it to create a MEF part. Depending on the predicate you could end up matching a single type or every single type available in the application domain.
But what if you wanted to make sure that the conventions could only be applied to a specific subset of all the available types? This is where type loaders can help you out. A type loader is used to define the subset of types that you want your conventions to have access to. Not that this does not mean that the conventions will be applied on each of those types only which types that the predicate is evaluated against.
The functionality of a type loader is defined by the ITypeLoader interface, which consists of two methods; AddTypes and GetTypes. When adding types to the type loader, you pass in a Func<Type[]> function, which will then be evaluated when MEF asks the convention based programming model for the parts it can provide. This basically means that you get deferred evaluation of which types that are available, giving you the ability to make a runtime decision.
There is a default implementation of the ITypeLoader interface called TypeLoader that should be enough for most needs, but just as with the convention primitives you could just as well create your own implementation if you need custom functionality. The convention based programming model will have no trouble consuming custom implementations.
The following example shows how a type loader could be used to restrict the available types to the types of the current executing assembly.
var loader = new TypeLoader();loader.AddTypes(() => Assembly.GetExecutingAssembly().GetExportedTypes());
Later versions will include a configuration API for the type loader, that will look something like this
var loader = new TypeLoader();loader.Configure(x =>{ x.AddExecutingAssembly(); x.AddAssembly(@"SomeAssembly.dll");});You will be able to write extension methods, for the configuration closure, to provide additional ways to add types to the type loader. The last thing to say about the type loader is that they are used on a catalog level and not on a registry level. This means that all the conventions, used by the catalog, will be restricted to the types that the type loader instance, passed into the catalog, makes available.
ConventionCatalog
There really is not much to say about the catalog used by the convention based programming model. You use is just as any other MEF catalog, the difference comes in the parameters that you provide when creating an instance of it. That catalog is called ConventionCatalog and it has two constructor two overloads; one that accepts a collection of part conventions along with a type loader and the second one that accepts in a collection of part registries along with a type loader.
The purpose of the catalog is to take the available conventions and turn them into ComposablePartDefinitions, a sort of schema of the part and its imports and exports, that is used by the container to figure out how all things are connected with each other. The container queries the catalog of all the part definitions that it can provide.
ConventionPart
One of the core types in MEF is the ComposablePart type. It is one of the main actors in the composition mechanism and is responsible for satisfying the imports on instance that it represents, which includes stuff like casting a collection export definitions into the correct type of the importing member. The latter sounds like a simple cast when you first look at it but there is more to it than meets the eye. Taking an arbitrary collection of values and converting them into something that can be assigned to an importing member requires quite a bit of plumping. Notice that I specifically used the term convert and not cast, this is because sometimes it involves dynamically creating an instance of specific collection type, casting all values into a type that the collection can store and then populate it.
The ComposablePart is also the type that allows existing instances to be composed, unfortunately the implementation used internally by MEF is marked as Internal along with the supporting infrastructure code to that makes it tick. It is also built around the concept that the instance, you want to compose, carries (remember the standard way for defining imports and exports are by using attributes) the information required to create a ComposablePartDefinition.
This means that if you want to perform composition on types that does not carry the information on itself (such as in the case where the conventions are defined separate to the types), you will have to implement a custom ComposablePart and that requires quite a bit of work. It can be done, I have done it once before for the provider based programming model, however the current version of the convention based programming model does not have a custom ComposablePart implementation.
To overcome this in the first preview of the convention based programming model, a “buddy”-part was introduced to bridge the gap between the two models. The ConventionPart<T> type is an attributed type used to perform composition using conventions. The type is built in such a way that it has a public property called Imports which is a collection of the type T and has been decorated with the ImportMany attribute. The type of the generic parameter T is the type you want to compose using the conventions.
Passing this into the container will have it satisfy the Imports property and all related imports on exports, in this case those that are defined on the type T, using all the available ComposablePartDefinitions. The type we are really interested in is T so once the composition has taken place we can get one or more composed instances of T from the Imports property.
To use it, create a new instance the type you want composed and pass it into the container, just as any other instance.
var part = new ConventionPart<ApplicationHost>(); var container = new CompositionContainer(catalog); var batch = new CompositionBatch();batch.AddPart(part); container.Compose(batch); var composedInstance = part.Imports.First();
Default conventions and community feedback
I mentioned earlier that the registry DSL is a bit verbose and that there are several ways in which it could be a lot simpler. The main reason for it being so verbose and raw is that it provided a nearly one-to-one mapping against the convention primitives a long with some additional overloads.
There is also another thing to consider and that is default conventions. What conventions could be included to represent default conventions? Your idea of default conventions are probably not the same as the next guys. It is not a trivial task to identify a set of conventions that can be reused across multiple projects by different people and companies.
I am not saying that it is impossible to identify some common conventions that can be packed up and shipped with a later release, in fact I am sure there are. This is where I will turn to the community and ask for you to help try to identify things that could be turned into reusable items, such as conventions and extensions.
Speaking of extensions, the entire registry DSL is built to support of rich extensibility model though the help of extension methods. I am certain that there are lots of useful extensions that could be written for it and I would very much like to hear back from you with ideas and if you can provide the implementation yourself then that would be even better. I will make sure to include contributions that could be useful for others to have.
For example with a bit of extension method magic, the following line
.ForTypesMatching(x => x.GetInterfaces().Contains(typeof(ILogger)))
could be transformed into
.ForTypesMatching(x => x.Implements<ILogger>());
here an extension method has been added to the type class, to return a boolean value based on if the type implements the provided interface or not. This is an extension method to help define a predicate, but there is nothing stopping you from continuing up one level and apply an extension method to the DSL itself. So for example you could remove the entire ForTypesMatching predicate method with a custom one
.ForTypesImplementing<ILogger>()
this is a much more readable approach. I could see a scenario where you would like to add multiple custom predicate calls in your part convention declaration and I might change the predicate parameter into a collection of predicates in a later release. Let me know if this is something that would be useful to you!
Another example of how the same clean-up can be made for import/export predicates such as
.Members(m => m.GetProperties().Where(p => p.ReturnType.Equals(typeof(ILogger))))
that identifies all properties that returns an ILogger instance, could be turned into
.Members(m => m.PropertiesOfType<ILogger>())
or again, move the extension method into the registry DSL itself and you end up with
.PropertiesOfType<ILogger>()
I definitely plan on including these kind of extensions in later releases and with your help that could turn out to be a sooner than later event! Just to give you an example of what the previous registry DSL sample could look like, if you applied some extension method magic to it, have a look below
public class ExtensionRegistry : PartRegistry{ public ExtensionRegistry() { Part() .ForType<ApplicationHost>() .ExportType(); .MakeShared() .ImportProperty<ApplicationHost>(x => x.Extensions); Part() .ForTypesImplementing<IApplicationExtension>() .ExportTypeWithContract<IApplicationExtension>(); }}What is next
Like I mentioned, there are a couple of known limitations, such as no support for constructor imports and not being able to compose instances with the help of the conventions. Other than that, I would really like to let community feedback drive the roadmap of this project. That said, I would love to hear everything about your experience and issue from using the convention based programming model, so drop me a line!
Furthermore, if you create any useful registry DSL extension method or default convention implementations – please consider sharing them with the community. Send me a line and I will see about getting them into the source code. Even if you only have ideas on extensions, but lack the time or skills to create them, contact me anyway and I will see about creating an implementation as soon as I have the time.
Over the next couple of weeks I am going to try to start a small blog series about the convention based programming model and highlight certain things in each of the posts. So keep an eye out, download the source code and start playing with the bits, blog about your experience and let me know.