Introduction
This article describes a component framework. Component based programming is used to create loosely coupled classes that are easy to test and maintain. Components usually get their dependencies in their constructors, but can also get them assigned by properties.
A component framework is used to create and maintain components and handle dependencies. Most frameworks can create a new instance every time or let components be singletons, which means that the same instance is returned every time.
The framework uses dependency injection (currently only constructor injection) to add dependencies. You can read Martin Fowler's article about Dependency Injection and Inversion of Control here.
Background
I love to learn and improve my coding skills. I usually do that by trying to do everything myself, and that's why I create my own framework. I know there are a dozen different component frameworks out there (for instance, the Castle project), and this is my contribution. This is my second attempt where I try to fix design errors in my previous attempt.
If you have taken a look at my web server, you'll know that I like to (try to) write code that is extensible and modular. This framework is no different. I'll show you how to load components using attributes, load them using app.config, load components from external assemblies, use versioned components, create remote components (components that are invoked client side, but executed server side or vice versa), and finally how to add internal components (components that can only be retrieved from within the same assembly as they were declared in).
Using the code
The central class in the framework is called ComponentManager
, which is used to create and access components. It uses Reflection to identify and load dependencies. It cannot read config files or automatically find components; external helper classes are used for that.
Components are defined using an interface type and an instance type. The interface type is used to access the component, while the instance type is used to create it. The ComponentManager
can either create components at startup (use the CreateAll
method) or when components are requested. If you use the latter, you might want to run the ValidateAll
method to make sure that all component dependencies have been met.
Loading components automatically
To load components automatically, we use a class called ComponentFinder
which can scan external assemblies, or assemblies specified in the method call, for components. It scans all assemblies for classes with the Component
attribute.
In the following example, we have a message component implementation which sends messages as emails. The component has a user manager and an SMTP server as dependencies. The dependencies will be assigned by the ComponentManager
, and those components will be created automatically by the manager if they have not been previously created.
[Component(typeof(IMessageManager))]
public class EmailManager : IMessageManager
{
SmtpServer _server;
IUserManager _userManager;
public EmailManager(IUserManager userMgr, SmtpServer smtp)
{
_userManager = userMgr;
_server = smtp;
}
public void Send(IMessage msg)
{
string email = _userMgr.Get(msg.FromUserId).Email;
_server.Send(email, msg.To, msg.Subject, msg.Body);
}
}
Creating a ComponentManager
and loading components is easy:
ComponentManager mgr = new ComponentManager();
ComponentFinder finder = new ComponentFinder();
finder.Find(new List<assembly>() { GetType().Assembly });
mgr.Add(finder.Components);
IMessageManager messageMgr = mgr.Get<IMessageManager>();
Load from app.config
Components can be defined using app.config, where simple constructor parameters can also be specified.
="1.0"="utf-8"
<configuration>
<configSections>
<section name="Components"
type="Fadd.Components.ConfigSectionHandler,Fadd.Components"/>
</configSections>
<Components>
<Component
Interface="Fadd.Components.Sample.SmtpServer, Fadd.Components.Sample"
Instance="Fadd.Components.Sample.SmtpServer, Fadd.Components.Sample"
IsSingleton="true">
<Parameter Name="hostName">smtp.yourserver.com</Parameter>
<Parameter Name="port" Type="System.Int32">25</Parameter>
</Component>
<Component
Interface="YourApplication.Shared.IMessageManager, MessageManagers"
Instance="MessageManagers.Email.Manager, YourApplication" />
</Components>
</configuration>
The config file will load two components. The SmtpServer
has hostName
and port
arguments in its constructor. We are using an email manager in this example, but we could easily switch to something else by just modifying the config file. This is possible since everything else accesses the interface IMessageManager
and not the implementation.
Loading components from external assemblies
Again, we'll use ComponentFinder
to find components. This time, we'll scan all DLLs in the application folder for components. This scanning is done in a separate AppDomain to avoid all scanned assemblies getting loaded into the primary app domain.
ComponentFinder finder = new ComponentFinder();
finder.Find("*.dll");
componentManager.Add(finder.Components);
Versioned components
Let's say that a third party programmer has created a really nice assembly with lots of useful components. The problem is that one of the components must be replaced while all others will work fine. You could create your own ComponentFinder
which just ignores the component in question. Or, you could specify a new implementation with a higher version number.
[Component(typeof(IUsefulComponent), 2)]
class MyReplacement : IUsefulComponent
{
}
You can have as many implementations as you like, but only the one with the highest version number will be used.
RunAt attribute
There is another way to determine which implementation to use. And, that's the RunAt
attribute. You might want to use one implementation at client side which contacts another implementation at server side.
In this example, we have a user manager which not only retrieves users from the database, but also keeps state information etc., in memory. This means that the client cannot use the same component, since states would then differ between the client and the server.
[Component(typeof(IUserManager), "Server")]
public class ServerUserManager : IUserManager
{
}
[Component(typeof(IUserManager), "Client")]
public class ClientUserManager : IUserManager
{
}
Remoting
I have not had the time to look at dynamic proxies yet, but my intention is to be able to create remoted components dynamically at runtime in later versions of the framework. I have come up with a solution for events, but I seriously don't know if I will implement it, since remoted events can give a big performance hit server side.
Remoting is currently done by using RemotingChannel
s; you need to create one client side and one server side:
private void Remoting()
{
ComponentManager server = SetupServerRemoting();
ComponentManager client = SetupClientRemoting();
string myMessages = client.Get<IMessageManager>().GetMessages();
}
private ComponentManager SetupClientRemoting()
{
ComponentManager clientManager = new ComponentManager {Location = "Client"};
ComponentFinder finder = new ComponentFinder();
finder.Find(new List<Assembly>() { GetType().Assembly });
clientManager.Add(finder.Components);
RemotingChannel client = new RemotingChannel(clientManager, false);
client.Start(new IPEndPoint(IPAddress.Loopback, 8334));
return clientManager;
}
private ComponentManager SetupServerRemoting()
{
ComponentManager manager = new ComponentManager { Location = "Server" };
ComponentFinder finder = new ComponentFinder();
finder.Find(new List<Assembly>() { GetType().Assembly });
manager.Add(finder.Components);
RemotingChannel server = new RemotingChannel(manager, true);
server.Start(new IPEndPoint(IPAddress.Loopback, 8334));
return manager;
}
The client side component is basically a skeleton which uses the remoting channel, and looks like this:
[Component(typeof(IMessageManager), "Client")]
class ClientMessageManager : IMessageManager
{
private readonly RemotingChannel _channel;
public ClientMessageManager(RemotingChannel channel)
{
_channel = channel;
}
public void Send(string receiver, string message)
{
_channel.Invoke(typeof(IMessageManager),
"Send", receiver, message);
}
public string GetMessages()
{
return (string)_channel.Invoke(
typeof (IMessageManager), "GetMessages");
}
}
Internal components
When you start with component based programming, you'll most likely have private
/internal
components that should only be accessible from within the declaring assembly. But, you want to take advantage of the component system to create and access your internal components and fulfill their dependencies. This can be done when you add a component by passing the ComponentFlags.Internal
flag in the method call.
Specialized startup
I have components that need to be started when all components have been added and created. This can be achieved by first defining a interface:
public interface IStartable
{
void Start();
}
Then, let all your components that need to be started implement that interface. And finally, we use a method called VisitAll
to start the components.
_manager.VisitAll((type, instance) => { if (instance is IStartable)
((IStartable) instance).Start(); });
Final words
The code is still very young (a couple of weeks), and there are probably a few bugs that need to be ironed out. But, the code should be usable, and my intention is to remove all bugs as soon as they are found by you and me.
The project can also be found at CodePlex.
History
- 2009-03-10: First version.