Just about anyone who has programmed in the .NET Framework has come across the System.ComponentModel namespace. The Windows Forms, ASP.NET and data classes all implement interfaces from this namespace. But other than that, most people dont know what this namespace is for. Most think it is just a random collection of stuff necessary to support designers. Well, that's partially true: designers make extensive use of this namespace. System.ComponentModel is not just for designers, however. It provides a group of generally useful interfaces and classes you can use in your own applications. In current versions of the .NET Framework all we've really done with component model is provide a useful design pattern; we haven't really made much use of it outside of designers. This post will show you how you can use this namespace in your own applications, utilizing the same design patterns we've used in the designer.
Components, Sites and Containers
At the core of System.ComponentModel is an interface called IComponent. IComponent doesn't have much to it: it derives from IDisposable, offers a Site property and raises an event when the component has been disposed. Anything that implements this interface is called a component. I've drawn a picture relating components, sites and containers below:

Components have two important characteristics:
- They can be owned by a container
- They can retrieve services from the container that owns them
The first characteristic doesn't get a component very far, but it does allow a component's lifetime to be controlled by a container. The second characteristic is far more interesting. It allows a component to gain access to services from other parts of the application. A service is simply an instance of an object that is stored in a dictionary keyed off of the object's type. One part of an application can provide a service for another part of the application to use. The power of services lies in their loose coupling: an application publishes the interface or base class that defines the service, but does not publish the class that implements the service. Components can request the service and retrieve an instance they can use, but never have to know about the actual implementation nor where it comes from. This design pattern allows you to develop extremely large applications because each of the parts of the application is only loosely coupled to the other.
Visual Studio makes extensive use of this service pattern for software design, as do the designers we provide within the framework. You can use this pattern in your own applications, too. As an example of this, lets take a look at a service that is already defined in Windows Forms: the AmbientProperties service.
Windows Forms AmbientProperties Service
Try this: change the font of a Form and then place a button on the form. What happens? The button receives the forms font. Why? Because all controls in Windows Forms have several properties that, if unset, ask their parent for a value. This allows you to set fonts and colors once, and have them flow down to child controls. Of course, if you don't set a font on the form a default font is used. Where do you think Windows Forms gets the default font? It actually checks two places: first, it checks to see if it can get to a service that will tell it what font to use. If it cant get to this service it then asks Windows for the default dialog font for the user.
The service Windows Forms looks for is called AmbientProperties and has been sitting in the Windows Forms namespace since the framework originally shipped. By using a container to site all of the dialogs in your application, you can setup application-wide fonts and colors. Our first step is to modify Main so we can create a container for our application:
[STAThread]
static void Main()
{
AppContainer c = new AppContainer();
Form1 f = new Form1();
c.Add(f);
Application.Run(f);
}
AppContainer is an internal class I wrote that derives from Container. It implements a single service: AmbientProperties. The entire class is shown below:
internal class AppContainer : Container
{
AmbientProperties _props;
protected override object GetService(Type service)
{
if (service == typeof(AmbientProperties))
{
if (_props == null)
{
_props = new AmbientProperties();
_props.Font = new Font("Arial", 16);
}
return _props;
}
return base.GetService(service);
}
}
If you run this code, and have controls on Form1, youll see that their fonts have enlarged to be 16 points in size. In addition, you can make other forms also have this same behavior by siting them as well. The following code does this in a button click handler for a button I have on form1:
Form2 f = new Form2();
if (Site != null) Site.Container.Add(f);
f.ShowDialog();
You can add other services to the AppContainer class that can be used elsewhere in your application.
Service Containers
In the above example I showed you a really simple mechanism for providing services. Even this simple mechanism has a big advantage: the service wasn't created until someone asked for it. This is what allows large applications to scale well: they can have vast lists of services but those services are not actually instantiated until someone needs them. This pay for play enhances the performance of the application.
In my previous example I specifically check for a service type of AmbientProperties and create the object on demand. This technique has its drawbacks, however. For one thing, as the number of services I want to supply increases, I need to forever expand my if statement. More importantly, only my main method today can offer services. One very powerful concept of the service mechanism is that anyone can provide services to anyone else. The component model namespace can help here too: in addition to service providers, it also defines a service container. As their name implies, service containers contain a table of services. Perfect. Even better, service containers support delayed instantiation of service objects. Lets look at what it would take to change my sample to use service providers.
First, I am going to use a pre-built class in System.ComponentModel called ServiceContainer, that implements the IServiceContainer interface for me. I will plug this into my AppContainer class as follows:
internal class AppContainer : Container
{
ServiceContainer _services = new ServiceContainer();
internal IServiceContainer Services
{
get { return _services; }
}
protected override object GetService(Type service)
{
object s = _services.GetService(service);
if (s == null) s = base.GetService(service);
return s;
}
}
Notice that my AppContainer class no longer has any code in it to handle AmbientProperties. It simply routes any service requests into the service container, and calls base if the service wasn't found in the service container. Now that I've done this, I need to add AmbientProperties back into the service container. I do this in Main:
[STAThread]
static void Main()
{
AppContainer c = new AppContainer();
AmbientProperties p = new AmbientProperties();
p.Font = new Font("Arial", 16);
c.Services.AddService(typeof(AmbientProperties), p);
Form1 f = new Form1();
c.Add(f);
Application.Run(f);
}
A quick run of the code shows that I still have my big 16 point font in my dialog. But, I lost something here. I've lost the pay for play feature I had before. Now, even if no one ever asks for AmbientProperties, I've still created it, and that takes resources. I'd like to only create AmbientProperties if someone asked for it. Can I still use service containers? You bet! Service containers can accept a callback delegate in place of a service instance. Lets just modify Main a bit more:
[STAThread]
static void Main()
{
AppContainer c = new AppContainer();
c.Services.AddService(typeof(AmbientProperties),
new ServiceCreatorCallback(OnCreateService));
Form1 f = new Form1();
c.Add(f);
Application.Run(f);
}
Here I've supplied a callback delegate instead of an instance of AmbientProperties. This delegate will be invoked the first time someone asks for AmbientProperties. My implementation of OnCreateService is pretty straightforward too:
private static object OnCreateService(IServiceContainer c, Type service)
{
AmbientProperties p = new AmbientProperties();
p.Font = new Font("Arial", 16);
return p;
}
The final thing that ServiceContainer provides for me is an instance of IServiceContainer as -- you guessed it -- a service! Anyone who is sited in my AppContainer class can call GetService(typeof(IServiceContainer)) and add their own services to the application. In the next section I'll demonstrate how you can put that to use.
Services in your Application
Using the IServiceContainer service provided by ServiceContainer (wow, thats a lot of "service" terminology) you can add services from anywhere in your application. For example, say youve written an MDI application and you have a status bar on the bottom of your main MDI window. You can add the status bar as a service in your forms Load event:
private void Form1_Load(object sender, System.EventArgs e)
{
IServiceContainer s = GetService(typeof(IServiceContainer)) as IServiceContainer;
s.AddService(typeof(StatusBar), statusBar1);
}
Then, you can use the status bar in other MDI children within your application:
private void Form2_Load(object sender, System.EventArgs e)
{
StatusBar sb = GetService(typeof(StatusBar)) as StatusBar;
if (sb != null) sb.Text = "My Status";
}
Notice that I am checking for null when I call GetService for the status bar. This is one of the fundamental things to remember about services: you should have some sort of fallback if the service isnt around. Here, the consequences of not having status text are not major, so I simply handle null by doing nothing. If I needed the service as a requirement of my application, I would probably display an error to the user, or log an error to the event log so support personnel could diagnose the problem.
Wrapping Up
Well, hopefully your eyes aren't glazing over yet. The component model provides a simple design pattern you can use in your applications. Using containers and sites to offer services to the various parts of your application can help your development process scale better as your application becomes more complex. Once you get into the service groove youll find them quite flexible and easy to use.