Recently I was working on an application integration design wherein I wanted to expose service operations as extensibility points. By opening up the service operations for extensibility I wanted to create a simple message bus which can be tapped in to for application integration needs without making any changes to the existing implementation. The extensibility point will expose the operations of service to other components which will get chance to act upon service requests.
I considered few design approaches to accomplish this, however I found most of the approaches to be intrusive to existing implementation. I was looking for something which will be loosely coupled to the existing service implementation and really be like plug-and-play, without much code changes.
First obvious thought was to look at IoC(Inversion of Control) and DI(Dependency Injection) along with WCF extensibility points. After looking all the available WCF extensibility points it was easy to zero down on Parameter Inspector (IParameterInspector) as it gives access to the operation name and parameters. If we know the operation being called along with parameters and all the extensions implemented for that specific service, it would be just matter of loading the extensions and calling the operation.
When you look at IoC/DI space there are few seemingly overlapping options from Microsoft itself, like Unity application block from P&P, Managed add-in framework (MAF) in System.AddIn namespace and the latest entrant Managed Extensibility Framework(MEF). At high level, all of these are composition engines, however MEF looked like specialized IoC container with added bells and whistles around DI. MEF is optimized around ‘discovery of unknown parts’ rather than just ‘registration of known parts’, this is what made MEF an interesting option for the problem I was trying to solve. Another consideration was that MEF is bundled with .Net Framework 4.0, so no more libraries to update and move around. There is a nice post aboutTen Reasons to use the Managed Extensibility Framework.
After little bit playing around with MEF, following looked like the emerging pattern for the problem I was trying to solve.
I defined an attribute(named ‘ServiceOperationExtensibility’) which implements IOperationBehavior interface and adds IParameterInspector DispatchBehavior. This attribute can be applied to an operation which needs to be exposed as extensibility point as shown below.
1: [OperationContract]
2: [ServiceOperationExtensibility]
3: string OperationA(int param1, string param2);
A class implementing IParameterInspector (named ‘ExtensibilityInspector’) will accept the service contract type as constructor parameter.
1: ExtensibilityInspector extensibilityInspector = new ExtensibilityInspector(operationDescription.DeclaringContract.ContractType);
2: dispatchOperation.ParameterInspectors.Add(extensibilityInspector);
The ExtensibilityInspector simply gets instance of ExtensibilityManager and passes the service contract type, operation name and arguments in ‘BeforeCall’ method of IParameterInspector interface.
1: public object BeforeCall(string operationName, object[] inputs)
2: {
3: ExtensibilityManager.Instance.InvokeExtension(this.ServiceContractType, operationName, inputs);
4: return null;
5: }
The ExtensibilityManager is responsible for discovering and loading the extension components and calling the operation. The extension components needs to implement contract of service being extended. The component need to decorate the class with MEF ‘Export’ attribute, this attribute would allow it to be discovered by MEF catalog.
1: [Export(typeof(ProductService.IProductService))]
2: public class ProductServiceExtension1:ProductService.IProductService
3: {
4: //Implementation removed for brevity
5: }
The ExtensibilityManager creates MEF catalog from assemblies present in designated directory. So the extension components need to be simply dropped in to designated directory(I have named this directory ‘Extensions’).
1: private void Compose()
2: {
3: var Catalog = new AggregateCatalog();
4: Catalog.Catalogs.Add(new DirectoryCatalog(@".\Extensions"));
5: this.Container = new CompositionContainer(Catalog);
6: this.Container.ComposeParts(this);
7: }
InvokeExtension method of ExtensibilityManager created instance of MEF ImportDefinition based on the passed service contract type. It calls TryGetExports method on the previously created container to get all the extensions of specific type.
1: public void InvokeExtension(Type contractType, string operationName,object[] arguments)
2: {
3: ImportDefinition importDefinition = new ImportDefinition(i => i.ContractName.Equals(contractType.FullName), contractType.FullName, ImportCardinality.ZeroOrMore, false, false);
4: AtomicComposition atomicComposition = new AtomicComposition();
5: IEnumerable<Export> extensions = null;
6:
7: bool exportDiscovery = this.Container.TryGetExports(importDefinition, atomicComposition, out extensions);
8:
9: if (extensions != null && extensions.Count<Export>() > 0)
10: {
11: foreach (Export extensionExport in extensions)
12: {
13: //ToDo: spawn a thread and do work there instead of doing it on the calling thread
14: contractType.InvokeMember(operationName, System.Reflection.BindingFlags.InvokeMethod, null, extensionExport.Value, arguments);
15: }
16: }
17: }
After getting the extensions, we can invoke the operation on each of the extension components. Please note that in this sample I have directly called InvokeMember operation on the type instance, in production code you will spawn a thread instead of doing it on the calling thread.
Overall this turned out to be a good solution, though there is a space for improvement to make it more fault tolerant and scalable. Depending upon your scenario you need to build good security around the extensibility points and components, which I have completely omitted here.
The sample code can be downloaded from following link. The sample service(named ‘IProductService’) is hosted in IIS, you can use WCFTestClient to invoke the operations. I have added one extension component in the solution which simply writes an entry to windows event log on invocation of the operation.