We’ve offered extensibility points in Our DevForce product since the beginning of the century.
Our customers wrote classes that implemented certain of our interfaces. We discovered those classes by examining assemblies named in a configuration file (app.config or web.config). We had our own probing and injection logic because market forces precluded use of a 3rd party IoC.
In DevForce 2010 we’ve switched to the Microsoft Managed Extensibility Framework (MEF) which is baked into the .NET 4 platform.
How hard was that? It wasn’t a big deal. Mostly we had to figure out how it worked and find the best approach for us. Which is the point of this post.
Because we almost made a mistake. We didn’t know about MEF’s InheritedExportAttribute.
DevForce Extensibility
Let me set the stage.
Suppose you want to alter DevForce default behavior by supplying your own way of doing something. Maybe you want to supply a custom “LoginManager” to authenticate users. Maybe you want to supply a class that intercepts all attempts to save data to the database … so you can confirm the user’s right to save or impose some extra server-side validation or add audit records … whatever.
Heretofore, you wrote a MyLoginManager that inherited from IEntityLoginManager and stuffed it in a server-deployed assembly; call it "MyServerStuff". You next identified “MyServerStuff.dll” as a probe assembly in your web.config. We’d look there for a class implementing IEntityLoginManager and, upon finding yours, would instantiate and use your class instead of our default class. Similarly, you wrote a MyEntityServerSaving class that inherited from IEntityServerSaving and we’d find it too.
In MEF, MyLoginManager and MyEntityServerSaving are “parts”. They are parts that you “export” for DevForce to “import”.
The DevForce 2010 difference: we let MEF do the finding and creation of your types instead of doing that ourselves.
Our Initial Mistake
In a beautiful world, you – the developer of MyLoginManager and MyEntityServerSaving – don’t change a thing. Your classes just work.
Actually, it’s better than that. You can throw away the <probeAssembly> tags in the web.config and the app.config. That kruft was a repeated source of confusion and heartache for our customers. It’s gone. No <probeAssembly> needed if you drop your assembly on the server in the \bin directory.
We should have done that long ago. MEF made it obvious.
BUT … it looked like it wouldn’t be quite this easy. How would MEF know to import your MyLoginManager and MyEntityServerSaving?
We couldn’t figure that out. In vanilla MEF, you attach an Export attribute to a component that you want others to import. It seemed you’d have to decorate your custom classes with these attributes like this.
[Export(typeof(IEntityLoginManager))]
public class MyLoginManager : IEntityLoginManager { // your code }
Ugh. Why impose this burden on the poor developer? How many support calls would we field that ran something like this?
“Sir? DevForce didn’t find your class? Please open Visual Studio … now look at the class definition. Does it have an Export attribute? Yes an “Export” attribute. What? Oh yeah you’ll need an assembly reference and a using clause too. Sure, I’ll hold why you build, redeploy and run it …”
InheritedExportAttribute
Fortunately, I know how to complain. I wrote a “WTF” email to Glenn who graciously replied as follows:
You might want to look into InheritedExports. If you are not using metadata you can put an [InheritedExport] on an interface which will automatically apply to the exporter.
In other words….
[InheritedExport]
public interface IFoo {}
public class Foo : IFoo { ...}Foo above automatically exports IFoo since the decoration is on the interface. There are some issues if you are doing this combined with metadata.
How about that!
We added “[InheritedExport]” to all of our extensibility interfaces. Now our customer’s existing custom classes are discovered and work as they did before without modification. No attribute needed.
What if the extensibility point is a base class instead of an interface? That works too. Check this out. In DevForce, we have an EntityServerSaveInterceptor class that intercepts server side saves. It does some work by default … work that you can extend.
Here’s its new MEF’ed signature in DevForce 2010:
[InheritedExport( typeof(EntityServerSaveInterceptor) )]
[DefaultExport( typeof(EntityServerSaveInterceptor) )]
public class EntityServerSaveInterceptor {
Note the “DefaultExport”. That tells MEF to import an instance of this class if you don’t supply your own.
Customers can subclass our default version just as they did before:
public class MyEntityServerSaveInterceptor : EntityServerSaveInterceptor {
protected override bool ClientCanSave(Type type) { … }
}
They can’t MEF up.
Is MEF Legal?
You don’t know about MEF? Where have you been? How could you have missed Glenn “Mr. MEF” Block; the man is everywhere – print, stage, and screen – talking up the MEF magic.
You can read about MEF here and here and here and all over the place. Mike Taulty, who is excellent on any subject, produced some superb MEF screen casts. The once-obscure Dependency Injection technique has gone mainstream with MEF and will be arriving soon in a code base near you. You’ve probably been doing something like it for years and didn’t know it.
3 comments:
Interesting, I knew about InheritedExport but had not yet realized it could be applied to the contract interface itself!
However, your customer will still need to know about MEF attributes anyway if he needs to import other parts in his customized part. To eliminate the need for import attributes as well, I guess you'd have to write a custom export provider implementation.
Good observation about what my customer would need to do to import parts into his/her exported customization.
Our prior extensibility mechanism did not inject parts into customer extensions so this is not a migration issue.
I can imagine us enabling injection scenarios in future ... and these likely would require MEF import attributes on the custom part.
Our extensibility interfaces today typically prescribe methods whose parameters are set by DevForce. For example, DevForce supplies the user Principal as an argument to a query checking method.
I'm not seeing the need to inject anything into the extension just yet ... but its great to know we could.
Yes Wim, they would have to use Import.
However, the primary use case for InheritedExport is for existing infrastructure which is migrated to use MEF. In those cases it is likely the contract types express that services or a locator.
Post a Comment