After playing around with MEF for a while, you begin to find more and more locations where it could be used to extend your application. It works great for a large number of scenarios and provides a method for others to extend your apps in a way as simple or complex as you want to make it for them. However, anyone who has sought community participation can tell you that the best way to get such is to make thing as easy as possible for a member of the community to participate. If you have the greatest, extensible application in the world but require 50 steps to get a part added to the application, the user community may not be very prone to build extensions for such. Alternatively, if the work required to extend the application was whittled down to only a few steps, the probability of participation rises. In this blog post, we'll look at this pattern and show how using MEF's InheritedExport attribute can be used to make part creation extremely simple for their authors.
Reviewing Previous Examples:
In previous examples of this series, we created parts that would be placed into a directory and imported into the app based on how the classes were decorated. By explicitly decorating our parts with the Export attribute works well since it gives us the ability to say which classes to import and which not to; however, in order to use the Export attribute we also had to include a reference to the System.ComponentModel.Composition assembly & namespace. Depending on how we setup our part contacts, we would also need to add a reference to our application as well. While that's not a lot of setup, it adds up since it has to be done every time a new part has to be created. If the parts were created as part of a framework or engine, then it would be in a person's best interest to create a VS template or some other form of code generation or snippets to automate as much of that ceremony as possible. But what if there was a simpler way to address this from within the application we're trying to extend?
Simplification through Inherited Exporting:
One of the lesser-documented items contained within MEF is the InheritedExport attribute. This attribute flags all derived classes as parts that fit the contract defined by it. So instead of requiring every part that implements a particular interface or base class to use the Export attribute and settings, you can just decorate the interface or base class with the InheritedExport attribute and then every class is considered to be an Exported part by default. If you are familiar with WCF, this is similar to the practice of creating an Interface for declaring your data contracts instead of directly decorating concrete classes. Below is an comparison between the way that has been show thus far and using the InheritedExport attribute.
Previous Example:
1: [Export("commands", typeof(ICommand))]
2: public class Command1 : ICommand
3: {
4: // Implementation of the Interface
5: }
6:
7: [Export("commands", typeof(ICommand))]
8: public class Command2 : ICommand
9: {
10: // Implementation of the Interface
11: }
Inherited Example:
1: [InheritedExport("commands",typeof(ICommand))]
2: public interface ICommand
3: {
4: // Interface Declaration
5: }
6:
7: public class Command1 : ICommand
8: {
9: // Implementation of the Interface
10: }
11:
12: public class Command2 : ICommand
13: {
14: // Implementation of Interface
15: }
Without the requirement of marking all of the parts with the Export attribute, the developers who are creating extensible parts for your application will no longer need to reference the System.ComponentModel.Composition namespace. In addition, the risk of mistyping the contract information in the Export attribute is removed. By using the InheritedExport attribute, the only requirement for authoring parts becomes just a reference to the assembly containing the decorated base class or assembly.
A Useful Pattern of Inherited Exporting:
While the InheritedExport attribute provides a simplified and safer method of handling part creation, it is an All-or-Nothing mechanism. While there are ways to exclude parts using different catalogs, there is no way to explicitly exclude a part like you could when we were using the Export attribute on all parts. A good way around this issue is to base your part contract/type on an interface and add the InheritedExport attribute to an abstract base class that implements said interface. This provides the blanketed support and convenience of using InheritedExport while providing authors an advanced mechanism by directly implementing the interface and adding the Export attribute manually if needed. Below is an example of this pattern.
1: public interface ICommand
2: {
3: // Interface Declaration
4: }
5:
6: [InheritedExport("commands", typeof(ICommand))]
7: public abstract class CommandBase : ICommand
8: {
9: // Abstract implementation of the Interface
10: }
11:
12: public class Command1 : CommandBase
13: {
14: // Implementation of the Abstract Class
15: }
16:
17: public class Command2 : CommandBase
18: {
19: // Implementation of the Abstract Class
20: }
Summary:
In this post, we looked at how to streamline and simplify our part definitions by using the InheritedExport method instead of just the Export attribute. We looked at the benefits of this simplicity and how it can help make things easier for part authors as well as identified a possible fault with using the InheritedExport attribute at the wrong level of abstraction. In the corresponding example project linked below, the concepts discussed above are applied to a previous example project in order to illustrate some of the simple benefits that can be used within your application.
Nice job James. Turns out there actually is a way to prevent an InheritedExport from being discovered. It uses another lesser known attribute, [PartNotDiscoverable]. PND won't prevent parts that are added directly to the container, but should block discovery in the catalogs.
ReplyDeleteKeep up the great work, your series is really shaping up.
Hi,
ReplyDeleteThanks for great topic. I run into 2 issues that need to get clear.
1. In your example (Inherited Example:), there are 2 ExportDefinition discovered by the catalog: 1 is of the interface and 1 is of the derived class --> there are 2 instances of the derived class being created (I'm using DirectoryCatalog and only have 1 dll of derived class)
2. Can you adding more experience using InheritedExport together with MetadataView
Thank you