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 into 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 a 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 the 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 many code changes.
The 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 a 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 a 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 about Ten Reasons to use the Managed Extensibility Framework.
After a little bit of playing around with MEF, the 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 an 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 needs 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:
5: }
The ExtensibilityManager
creates MEF catalog from assemblies present in designated directory. So the extension components need to be simply dropped into the 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:
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 the 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.