Whilst looking at the Patterns & Practices GAT code, particularly custom wizard pages, I kept seeing lines like this:
IDictionaryService dictionaryService = GetService(typeof(IDictionaryService)) as IDictionaryService;
I'd never come accross this GetService method before, when I hit F1 I found out that it's a method inherited from System.ComponentModel.Component. Now, I thought I sort of understood the whole IComponent / IContainer thing, it's a pattern used by the IDE to allow you to drag and drop classes (components) on surfaces (other classes) allowing us to graphically compose applications. This is classic RAD stuff and it's really easy to use, you just implement IComponent, your class appears in the Visual Studio toolbox and if you double click on the class in the solution explorer it displays an design service which you can drag any other class that implements IComponent on to. I must admit I hadn't really thought of it much, or used that functionality other than as it's provided out of the box by the forms designer. I'm more of a POCO guy myself, I couldn't really see what benefits implementing classes as components provided other than that neat drag drop composition stuff.
After Googling for GetService I found this excellent post Lightweight Containers and Plugin Architectures by Daniel Cazzulino where he compares the IContainer pattern with lightweight container architectures described by Martin Fowler in this post. According to Mr Cazzulino the IContainer pattern provides the .net world with a very nicely designed component architecture that can be used for composing and sharing services at runtime and that's where the GetService method comes in. I wont repeat all of Daniel's excellent article which is definately worth reading, especially the class model which describes the relationship between IComponent, IContainer, ISite, IServiceContainer and IServiceProvider. Instead here's a little practical example of how a dumb server can have components, some of which may provide services to the other compoents to be configured at runtime using the IContainer pattern. The core trick is to extend the built-in Container to provide an instance of ServiceContainer:
using System; using System.ComponentModel; using System.ComponentModel.Design; namespace ComponentPlay { public class ServiceProvidingContainer : Container { IServiceContainer _serviceContainer; public IServiceContainer ServiceContainer { get { return _serviceContainer; } } public ServiceProvidingContainer() { _serviceContainer = new ServiceContainer(); _serviceContainer.AddService(typeof(IServiceContainer), _serviceContainer); } protected override object GetService(Type service) { return _serviceContainer.GetService(service); } } }
Then you can provide a server with your custom Container:
using System; using System.ComponentModel; using System.ComponentModel.Design; namespace ComponentPlay { public class Server { IContainer _components = new ServiceProvidingContainer(); public IContainer Components { get { return _components; } } } }
Then I've defined a simple service interface:
using System; using System.Collections.Generic; using System.Text; namespace ComponentPlay { public interface IMessageProvider { string Message { get; } } }
And a component that implements the IMessageProvider. The important thing here is to note how it overrides the Site property of Component to add itself to the service container, thus registering itself as an available service to other components (this is staight out of Daniel's post):
using System; using System.ComponentModel; using System.ComponentModel.Design; namespace ComponentPlay { public class MessageProvider : Component, IMessageProvider { string _message; public MessageProvider(string message) { _message = message; } public string Message { get { return _message; } } public override ISite Site { get { return base.Site; } set { base.Site = value; // publish this instance as a service IServiceContainer serviceContainer = (IServiceContainer)GetService(typeof(IServiceContainer)); if(serviceContainer != null) { serviceContainer.AddService(typeof(IMessageProvider), this); } } } } }
Here's a component that can use an IMessageService. Note that you cannot guarantee that the service will be available:
using System; using System.ComponentModel; namespace ComponentPlay { public class Client : Component { public void SayHello() { IMessageProvider mp = (IMessageProvider)GetService(typeof(IMessageProvider)); if(mp == null) Console.WriteLine("IMessageProvider is null"); else Console.WriteLine(mp.Message); } } }
And here's a little test program showing it all put together:
using System; using System.Collections.Generic; using System.Text; namespace ComponentPlay { class Program { static void Main(string[] args) { Server server = new Server(); Client client = new Client(); MessageProvider mp = new MessageProvider("Hello from the message provider"); server.Components.Add(client); server.Components.Add(mp); client.SayHello(); Console.ReadLine(); } } }
The cool thing is that with this pattern is that the server is simply a container and has to know nothing about what components and services it hosts. Services can register themselves without coupling to either the Server framework or to any other components and components can discover services without any intervention from the Server framework. It's really nice to see yet another usefull pattern that comes out-of-the-box with .net, I'm just surprised that it's not more widely advertised. It's really poorly documented with no real explaination in the MSDN library (or am I just missing something). I'd love to have found a good article explaining the design choices behind this pattern and some implementation examples.
No comments:
Post a Comment