Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A Lightweight .NET Component Framework in 100 Lines of Code

3.53/5 (12 votes)
2 May 20065 min read 1   155  
A minimalist approach to n-tier component model design and implementation, which provides great flexibility for application modules functionality separation.

Introduction

It's always fascinating to find a proper name for a thing, event, or some abstraction. Sometimes, words come first as formulas of ideas, and we then start looking for a way to materialize them. Design patterns metaphor is the way you organize the world around you (analysis), and simultaneously - the way you create new worlds (synthesis), words create worlds.

One of the most misleading and hidden things about class factories is how to articulate the idea behind creating class instances by means of some other class objects. The only reason for that should be a better control over these new instances through all of their life cycles (how many instances may be created, security restrictions, objects' ownership, etc.), and a better decoupling of the components implementation from the components consumer.

Another important consideration about the component model is to provide the possibility to get the current status information back from the component instance, running one of its methods asynchronously. Paraphrasing Alice: what is the use of a component without asynchronous callbacks?

In this article, we'll rather talk about constructs, we'll call them component factories, the name better explaining the essence of decoupling of the application architecture levels.

Decomposition and Design

We are going to present the 3-tier model:

  • master application or consumer;
  • middle tier implementing our components' creational strategy;
  • business logic tier containing different component (plug-ins) declarations.

All of the implementation tiers will reside in different assemblies.

The first thing we'll do is to describe a plug-in interface. We choose the IPlugin name just to indicate our component-oriented approach.

At this point, we'll provide a means to make both synchronous and asynchronous plug-in method calls. There are only two methods on the interface which will start the business logic routines: Do and DoAsync, performing synchronous and asynchronous calls, respectively. They both have one single argument - a string, containing the call parameters in any representation, e.g., in XML. This argument string is being parsed then, business classes are being instantiated, and the corresponding methods are being called.

In other words, the plug-in serves as a redirector, parsing requests and streaming data to the corresponding business modules. The plug-in itself, together with the business modules, will make up the business logic tier.

To introduce asynchronous calls, we need to invent a way to inform the application about the status of business logic calculations. We'll start asynchronous operations on a new thread. Whenever we need to inform a consumer about anything important, we'll fire an event from the plug-in, which should be caught in the consumer application. We'll apply event wiring based on the plug-in metadata, discovered through the Reflection API.

Now, we are ready to design a component factory with some creational strategy in mind. We will make this class factory an integral part of the interface library. This will decouple well, the business logic and the abstract middle tier, responsible for business logic creation and control, this way shaping out our own full-scale component model. We'll define the CreateObjectDirect method responsible for loading of a plug-in assembly and invoking of the static Create method of the test plug-in class, which will actually instantiate a new instance of the plug-in (extremely late binding). The CreateObjectDirect method will also wire up events originating in the plug-in to the callback method in the consumer application.

Proof of Concept

Our solution will consist of three projects containing the program modules listed below: ComponentFactoryTest (ComponentFactoryTest.cs) - contains consumer source code; PluginInterface (PluginInterface.cs) - interface, event class, event delegate; ComponentFactory.cs - component factory logic; TestPluginImplementation (TestPlugin.cs) - plugin redirector; TestBusinessLogic.cs - sample business module without user interface; TestBusinessForm.cs - sample Windows Forms based business module.

The IPlugin interface is fairly simple:

C#
public interface IPlugin
{
    int    Mode{ get; set; }
    void   Setup(string p_init);
    string Do( string p_XMLRequest );
    string DoAsync( string p_XMLRequest );
    bool   IsEventInitialized();
    string GetProductDetails();
    void   FireEvent(CEventArgs p_event);
    event  ProviderEventDelegate FireProviderEvent;
}

In the class factory implementation, the CreateObjectDirect method serves actually as a liaison between the consumer and the plug-in. It really makes sense to display the code:

C#
public Object CreateObjectDirect(string p_Assembly, 
              string p_ClassName, string p_InitData)
{
    IPlugin plugin = null;
    try
    {
        Assembly assembly = Assembly.LoadFrom(p_Assembly);

        Type tPlugin =  assembly.GetType(p_ClassName);
        if(null == tPlugin)
        {
            string info = "GetType error for " + p_ClassName;
            Console.WriteLine(info);
            return null;
        }
                
        object[] arguments = new object [] {p_InitData};

        // this is the way we call static Create method 
        // of the plugin implementaion
        plugin = (IPlugin)tPlugin.InvokeMember ("Create", 
            BindingFlags.Default | BindingFlags.InvokeMethod,
            null,
            null,
            arguments);

        if(plugin.IsEventInitialized())
        {
            return plugin;
        }

        Delegate eventDelegate = 
            Delegate.CreateDelegate(typeof(ProviderEventDelegate), 
            m_that, 
            "On_ProviderEvent");

        plugin.FireProviderEvent += (ProviderEventDelegate)eventDelegate;

    }
    catch(Exception e)
    {
        string info = ">> Exception in CreateObjectDirect : " + e.Message;
        Console.WriteLine(info);
    }

    return plugin;
}

From a consumer perspective, the calling sequence includes the steps below:

  1. Define a callback method to capture the events from the plug-in;
  2. Create component factory;
  3. Get an interface to a new instance of the plug-in with the CreateObjectDirect call, passing arguments referencing the plug-in assembly dynamic library;
  4. Call Do or DoAsync on this interface providing parameters necessary to invoke business logic methods;
  5. Receive status information from asynchronous calls via the callback method defined.

This is a sequence diagram, sketching the consumer, component factory, and the plug-in interaction.

Sample image

Please note that we run the Execute method on a business module instance on a dedicated thread.

The test application instantiates business logic plug-ins of two types - with and without a user interface. Both plug-ins simulate some business logic behavior by generating events. To fire events from the user interface-based plug-in, just press a button. These events are being caught in the consumer application, which prints out events' details. That's it!

Implementation Details and Considerations

To make the IPLugin interface accessible for both consumers and business logic modules, we shall declare it in a separate library, which we shall reference in all of our projects.

We will not sign the plug-in assembly with a strong name. We'll rather put the assembly into the same folder with the test application executable. Don't forget to do that if you want to change the implementation code.

In our approach, we will not pay much attention to the actual call parameter manipulations. We'll make things as clear as possible, implementing string transfers between the client and the component code. We will not even bother if it is an XML string. Any semantics of parameter string formatting may be reasonable and convenient. This approach has its own pros and contras, and might really be effective in a number of application scenarios.

Conclusion

We discussed a lightweight .NET component based framework, which will help us to develop n-tier, conveniently decoupled applications. This design provides enough flexibility for future enhancements and advancements. From the evolution perspective, we may start thinking about a container, holding newly created plug-in instances, adding reference counting to the IPlugin interface, providing remote access to the container, serializing the input/output data, etc.

Sounds familiar, doesn't it?

History

  • 05/01/2006 - Thanks for the comments submitted. The IPlugin interface now includes a FireProviderEvent definition, which improved the CreateObjectDirect code. Added the CreateObjectWithActivator method, just to illustrate other possibilities. Note that this function call requires the plug-in to have a public constructor, which may change the creational logic design.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here