Looking in the Box and at MEF-Contrib
So far in my MEF posts, I've really been focused on simple solutions that utilize the AssemblyCatalog and DirectoryCatalog objects. These specific catalogs provide a very fast and easy way of composing your various parts together. Part of their simplicity comes from how they handle the parts that it finds in the assemblies you provide it; namely, they take an All-or-Nothing approach. If they find parts that meet the defined contract criteria, they assume that they are going to be part of some composition. But what if you only wanted to use only a few parts located in a loaded assembly?This question was half of the problem I was trying to solve for my project. Looking around what's been done so far with MEF, I saw that I could address this part of my problem using a TypeCatalog. A TypeCatalog differs from other ComposablePartCatalogs in that it allows you to explicitly specify which Types you want to use as Parts. For example, in previous posts we showed "Help Text" of various parts that represented different commands/arguments of a command line application. If you didn't want Command #2, you could simply load all other commands into a collection explicitly and then pass the collection into a TypeCatalog.
Great, one problem solved, right? I can now explicitly load which parts I want to import; however, the second part of my problem was that I didn't want to recompile the code every time I wanted to change which parts were imported. At this point, I started to look around at what other people might have already done that would allow this type of configuration. I ended up looking at the Provider Model for MEF that was a part of the MEF-Contrib project out on CodePlex. What the Provider Model from MEF-Contrib does is move the part declarations (i.e. the Import and Export Attributes) to a custom configuration section. This held a lot of promise; however, ran into some trouble while working with it. After about a day of working with the MEF-Contrib provider model, I decided to try my hand at a different approach.
Adding a Layer on top of MEF
While I'm still determined to look into the MEF-Contrib provider model deeper in the near future, I also began to feel that mixing the attributed model and a provider model in the same application felt a bit dirty or smelly. I wanted the leverage the power and simplicity MEF's attributed model but be able to push it to the level of configuration that comes with a provider model. In order to reach this goal, I decided to add another layer of abstraction onto of the great foundation MEF provides for extensibility.In order to do this, I needed 2 things:
- A Custom Configuration Section to Identify the specific Types to import
- A ComposablePartCatalog that will grab these parts.
Creating the Configuration Section
I'm not going to go into a large amount of information on how to create a custom configuration section. There's a large amount of resources out on the Internet that can assist with such. What I needed was a simple section that took a collection of types. I wanted to truly keep things simple. What came out mapped to the following:1: <configuration>
2: <configSections>
3: <section
4: name="mef.configurableTypes"
5: type="MEFExample8.Core.Provider.ConfigurableTypeSection, MEFExample8.Core" />
6: </configSections>
7:
8: <mef.configurableTypes>
9: <parts>
10: <part type="MEFExample8.TestCommand1, MEFExample8" />
11: <part type="MEFExample8.Commands.TestCommand3, MEFExample8.Commands" />
12: </parts>
13: </mef.configurableTypes>
14: </configuration>
Creating the Catalog
Now that I have the section how I needed it, I had to create a catalog that will utilize it. While I could have create a custom catalog using the ComposablePartCatalog base class, I decided to just extend the TypeCatalog that comes with MEF. The TypeCatalog works great for such and the only thing I had to do was bring in the Types from the config file, load them into an IEnumerable<Type> collection, and then pass it to the TypeCatalog base. Simple right?As it turned out, I looked beyond my original needs and found two issues. The first was addressing what to do if the configuration section was named differently. The second issue was dealing with what to do if the catalog is instantiated more than once. Luckily, a simple solution to both was to allow the developer to pass in the section name into the catalog's constructor. This addressed the renaming concern while allowing multiple catalogs to point to different configuration sections.
Below is the catalog's code:
1: public class ConfigurableTypeCatalog : TypeCatalog
2: {
3: public ConfigurableTypeCatalog()
4: : base(GetTypes())
5: {
6: }
7:
8: public ConfigurableTypeCatalog(string sectionName)
9: : base(GetTypes(sectionName))
10: {
11: }
12:
13: private static IEnumerable<Type> GetTypes()
14: {
15: return GetTypes("mef.configurableTypes");
16: }
17:
18: private static IEnumerable<Type> GetTypes(string sectionName)
19: {
20: var config = GetSection(sectionName);
21:
22: IList<Type> types = new List<Type>();
23:
24: foreach (ConfigurableTypeElement p in config.Parts)
25: {
26: types.Add(Type.GetType(p.Type));
27: }
28:
29: return types;
30: }
31:
32: private static ConfigurableTypeSection GetSection(string sectionName)
33: {
34: var config = ConfigurationManager.GetSection(sectionName) as ConfigurableTypeSection;
35:
36: if (config == null)
37: {
38: throw new ConfigurationErrorsException(string.Format("The configuration section {0} could not be found.", sectionName));
39: }
40:
41: return config;
42: }
43: }
As I stated before, I decided to extend the TypeCatalog class. In order to extend it and meet the needs of my project, I overloaded the constructor so that I could set the default configuration section name (used in the configuration file snippet above) while allowing the second constructor to specify the section name. Since the constructor of the TypeCatalog class takes either a single Type or a collection of Types, I turned the code that retrieves that information from the configuration section into a static value. Since I'm not overriding any aspects of the TypeCatalog other than the constructors, I needed a way to pass the information to it as such and the static methods provided.
Notes about the Catalog:
At this point, I have a fully functional catalog that allows me to specify which types to include into a certain piece of my application. After I played around with it a bit I found that it worked well but had a few shortcomings that may cause people to wonder what's going on.The first scenario I came across was working with the catalog to reference parts in a subdirectory. This issue doesn't exist if the types you are referencing are in the same assembly or an assembly that's in the same directory as the executing program; however, if the assembly is in a subdirectory, you'll need to probe the directory as described in my previous MEF post - Using MEF and Custom Configuration Sections.
The second scenario came with the need for Type or Part exclusions. The code above uses the same attributed model that all out of the box catalogs use. This means that if you created an assembly with 3 attributed parts in it and used an AssemblyCatalog or similar one, all 3 parts would be identify and consumed. The custom catalog outlined in this post focuses on Type inclusion and not exclusions. Because of this, if the above catalog is combined in an AggregateCatalog, all of the parts that meet the contract will still be imported. Through this fact, I highly recommend using the above catalog separately from the others.
Thanks for the post. This is something I am looking into for one of my projects.
ReplyDelete