Click here to Skip to main content
15,881,856 members
Articles / Programming Languages / XML

Extensible component framework

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
18 Mar 2009LGPL35 min read 27.7K   497   31   2
A framework for component based programming (using dependency injection).

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.

C#
[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:

C#
ComponentManager mgr = new ComponentManager();

// Only load components from the current assembly.
ComponentFinder finder = new ComponentFinder();
finder.Find(new List<assembly>() { GetType().Assembly }); 
mgr.Add(finder.Components);

// And to access/create a component:
IMessageManager messageMgr = mgr.Get<IMessageManager>();

Load from app.config

Components can be defined using app.config, where simple constructor parameters can also be specified.

XML
<?xml version="1.0" encoding="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.

C#
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.

C#
[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.

C#
[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 RemotingChannels; you need to create one client side and one server side:

C#
private void Remoting()
{
    // This is typically done in your server.
    ComponentManager server = SetupServerRemoting();

    // Typically done in your client applications.
    ComponentManager client = SetupClientRemoting();

    // Invoke a method in your client to get it executed in your server.
    string myMessages = client.Get<IMessageManager>().GetMessages();
}

private ComponentManager SetupClientRemoting()
{
    // We'll create a new component manager for this example only.
    // Normally you have already created a component manager in your system,
    // which also is used for the remoting.
    ComponentManager clientManager = new ComponentManager {Location = "Client"};

    // Find all components in our current assembly. 
    // ComponentManager will only add components with the correct RunAt property.
    ComponentFinder finder = new ComponentFinder();
    finder.Find(new List<Assembly>() { GetType().Assembly });
    clientManager.Add(finder.Components);

    // Define where we should connect
    RemotingChannel client = new RemotingChannel(clientManager, false);
    client.Start(new IPEndPoint(IPAddress.Loopback, 8334));

    return clientManager;
}

private ComponentManager SetupServerRemoting()
{
    // We'll create a new component manager for this example only.
    // Normally you have already created a component manager in your system,
    // which also is used for the remoting.
    ComponentManager manager = new ComponentManager { Location = "Server" };

    // Find all components in our current assembly. 
    // ComponentManager will only add components
    // with the correct RunAt property.
    ComponentFinder finder = new ComponentFinder();
    finder.Find(new List<Assembly>() { GetType().Assembly });
    manager.Add(finder.Components);

    // Setup remoting, we should accept connections on port 8834.
    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:

C#
[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:

C#
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.

C#
_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.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
Generalgreat work! Pin
nommy_ahxh200018-Mar-09 2:16
nommy_ahxh200018-Mar-09 2:16 
GeneralTake a look at the MEF framework Pin
TimMerksem13-Mar-09 8:42
TimMerksem13-Mar-09 8:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.