KnowDotNet NetRefactor

Writing Plug-Ins for Loose Coupling of Application Components

Implement Interfaces and Use Reflection For Dependency Injection

by Les Smith
Print this Article Discuss in Forums

How do I write a Plug-in for an application and how do I know whether a DLL implements an Interface that my application expects?  Dependency Injection and Inversion of Control are hot topis in OOP development today.  These terms sound complex and their implementation can indeed be complex.  There are several ways of implementing Dependency Injection, but in this article I am going to describe a type of DI that is not normally covered under the definition of this term.

Simplistically, Dependency Injection means that an application learns about new dependencies at run time rather than compile time.  System architects often disagree as to the best way for an application to accomplish this.  One of the ways that I have found to be valuable, especially when I do not want to change anything in the base application, including the appConfig file is the use of Plug-ins whose contract verification is determined by the use of Reflection.  The reason that I like Plug-ins is that their names can be exposed in database table associated with an application/process and no changes are required for the base application.  Some argue that Dependency Injection should be done via the appConfig, but in most Enterprise environments, a change to the appConfig file is just as much a "code move" as an actual change to the application would be.   Strictly speaking, it should go through System Testing and Quality Assurance just as surely as any other code change

In this article, I will concentrate on the verification of the implementation of an Interface by a plug-in DLL.  It is possible that a DLL might be listed in database table but that it does not implement the specific Interface that an application or one of the processes of an application is concerned with.  Trying to call a DLL that does not Implement a specific Interface will obviously result in undesirable consequences.  

I am going to assume that we have a application class that processes batches of transactions from a database.  The design of the various tables used by this application is not relevant to this article.  I will assume three tables including a Batch table, Transaction table, and a Process table.  I will assume that this applcation will perform certain actions during the processing of a a batch and its associated transactions.  I will keep the number of actions and their respective processes simple so that I may concentrate on the mechanics of validating contracts.  Figure 1 shows a basic Interface (Contract) Class Library (DLL) project that represents the basic points within the application where I will allow a Plugin to be called, should one be found that wants to be called.

Figure 1 - Basic Interface

namespace PluginInterface1
{
  
public interface BasicActions
   {
      
bool BatchStartup(int batchNumber, int processNumber);
      
bool ProcessTransaction(DataRow dr);
      
void BatchComplete();
   }
}


Next, I will write a simple application that will process a batch and one transaction.  Normally, there would be a loop through the transactions in the batch, but I will ignore that because that is not relevant to showing how to use the plug-in.  Figure 2 shows the application that can call the plug-in.

Figure 2 - Main Application


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PluginInterface1;
using ValidateContract;
using System.Data;

namespace PluginApplication
{
  
class Program
   {
      
private static PluginInterface1.IBasicActions plugin;

      
static void Main(string[] args)
      {
        
// first find if a plugin exists
         object dllObject =
            
CheckForContract.VerifyPluginInterface(
            
@"C:\VS2008 Projects\PluginApplication\PluginTest\bin\Debug\PluginTest.dll",
            
"PluginInterface1.IBasicActions");
        
if (dllObject != null)
         {
            plugin = (PluginInterface1.
IBasicActions)dllObject;
         }

        
// later in the execution when the batch
         // is ready for processing
         // if plugin is not null, we already have
         // an instance of the pluging
         // and we can call any of its public methods
         // any time during the application
         // without using reflection again...
         if (plugin != null)
         {
            
int batchNbr = 12345;
            
int processNbr = 92;
            
if (plugin.BatchStartup(batchNbr, processNbr))
              
Console.WriteLine("continue processing batch");
            
else
            {
              
Console.WriteLine("plugin said to bypass the batch");
              
return;
            }
         }

            
// here we might be processing a transaction
         if (plugin != null)
         {
            
DataRow dr = null;
            
if (plugin.ProcessTransaction(dr))
            {
              
// plugin returned true, process the transaction
            }
            
else
            {
              
// plugin returned false, bypass the transaction
            }
         }
        
else
         {
            
// process the transaction normally, no plugin exists
         }

        
// finally, we have completed processing the
         // batch, just tell the plugin
         if (plugin != null)
         {
            plugin.BatchComplete();
         }
      }
   }
}

Next, I will write the Plugin Verification DLL.  Note that this component accepts a path to a DLL and the fully qualified Interface name as a string.  It returns an instance of the plugin DLL, if it implements the interface, as Type object.  It is then up to the main program to cast the plugin instance to the correct type before attempting to call its methods.  Without the cast you will not have Intellisense on the object.  I have left the casting of the object in the main program so that the Validation DLL can validate any Interface whose name is passed as a string.  Figure 3 shows the code for the Plugin Verification component.

Figure 3 - Validate Contract DLL

using PluginInterface1;
using System.Reflection;
using System.IO;
using System;

namespace ValidateContract
{
  
public class CheckForContract
   {
      
public static object VerifyPluginInterface(string pluginName,
        
string interfaceFullName)
      {
        
if (pluginName != null &&
            
File.Exists(pluginName) &&
            
Path.GetExtension(pluginName).ToLower().Equals(".dll"))
         {

            
// an Assembly is not an instance of the dll
            // it is an object that can be examined by reflection
            Assembly pluginAssembly = default(Assembly);
            
// attempt to create an instance of the object DLL
            // using reflection
            try
            {
               pluginAssembly =
Assembly.LoadFrom(pluginName);
            }
            
catch (Exception ex)
            {
              
return null;
            }

            
object dllInstance = CheckAssemblyForInterface(pluginAssembly,
               interfaceFullName);
            
return dllInstance;
         }
        
return null;
      }

      
private static object CheckAssemblyForInterface(Assembly plugin,
        
string interfaceFullName)
      {
        
//Type objType; // = default(Type);
         Type objInterface; // = default(Type);

         //Loop through each type in the DLL
         foreach (Type objType in plugin.GetTypes())
         {
            
//Only look at public types
            if (objType.IsPublic == true)
            {
              
//Ignore abstract classes
               if (!((objType.Attributes & TypeAttributes.Abstract) ==
                  
TypeAttributes.Abstract))
               {

                  
//See if this type implements our interface
                  objInterface = objType.GetInterface(interfaceFullName, true);

                  
if ((objInterface != null))
                  {
                    
string className = objType.FullName;
                    
object returnObject = plugin.CreateInstance(className);
                    
return returnObject;

                  }
               }
            }
         }
        
return null;
      }
   }
}


Next, I will write a simple plugin DLL project that will implement the three methods of the Interface.  Note that you must implement all methods of the Interface whether or not you have anything to do in them.  In this particular plugin, I will return true in the first two method, telling the main program that I want to process the batch and that I want to process the transaction.  Returning false would tell the respective methods that I do not want to process the batch or the transaction.  What this does is allows the plugin to alter the normal processing in the main application.  If there is no plugin, the batch and it's transactions will be processed.  However, the methods of the plugin could query database, etc., to determine whether or not to process the batch or particular transactions.  That's the beauty of the ability of run-time plugins to customize or modify the action of an application.  Figure 4 shows the code for the plugin.

Figure 4 - Plugin Code

using System.Collections.Generic;
using System.Linq;
using System.Text;
using PluginInterface1;
using System.Data;

namespace PluginTest
{
  
public class Class1 : PluginInterface1.IBasicActions
   {
      
public bool BatchStartup(int batchNumber, int processNumber)
      {
        
Console.WriteLine("BatchStartup in plugin was called");
        
return true;
      }

      
public bool ProcessTransaction(DataRow dr)
      {
        
Console.WriteLine("ProcessTransaction in plugin was called");
        
return true;
      }

      
public void BatchComplete()
      {
        
// do something when batch completes
         Console.WriteLine("BatchComplete in plugin was called.");
      }
   }
}

Finally, I will show the code in the main application.  In each set of code the "using" directives will show you the dependencies with respect to the various components of the five projects in this solution.  Figure 5 shows the code for the main application.

Figure 5 - Main Application

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PluginInterface1;
using ValidateContract;
using System.Data;

namespace PluginApplication
{
  
class Program
   {
      
private static PluginInterface1.IBasicActions plugin;

      
static void Main(string[] args)
      {
        
// first find if a plugin exists
         object dllObject =
            
CheckForContract.VerifyPluginInterface(
            
@"C:\VS2008 Projects\PluginApplication\PluginTest\bin\Debug\PluginTest.dll",
            
"PluginInterface1.IBasicActions");
        
if (dllObject != null)
         {
            plugin = (PluginInterface1.
IBasicActions)dllObject;
         }

        
// later in the execution when the batch
         // is ready for processing
         // if plugin is not null, we already have
         // an instance of the pluging
         // and we can call any of its public methods
         // any time during the application
         // without using reflection again...
         if (plugin != null)
         {
            
int batchNbr = 12345;
            
int processNbr = 92;
            
if (plugin.BatchStartup(batchNbr, processNbr))
              
Console.WriteLine("continue processing batch");
            
else
            {
              
Console.WriteLine("plugin said to bypass the batch");
              
return;
            }
         }

            
// here we might be processing a transaction
         if (plugin != null)
         {
            
DataRow dr = null;
            
if (plugin.ProcessTransaction(dr))
            {
              
// plugin returned true, process the transaction
            }
            
else
            {
              
// plugin returned false, bypass the transaction
            }
         }
        
else
         {
            
// process the transaction normally, no plugin exists
         }

        
// finally, we have completed processing the
         // batch, just tell the plugin
         if (plugin != null)
         {
            plugin.BatchComplete();
         }
      }
   }
}


Once the application is built and tested, including the verification of a single plug-in, you can write any number of plug-ins without having to touch the base application.  Some do not like the thought of using Reflection at run-time, saying that Reflection is slow.  However, you can see from the example that Reflection is used only once in the application and from then on, the application simply tests whether there is a plug-in object to call and if so, it calls it.  No big deal.  Sure, I have to go through unit and system testing of any new plugin, but I never touch the application or its appConfig file and discovery of new plug-ins is totally dynamic and seamless to the base application.

Have you tried our newest product, Visual Class Organizer?  You'll be amazed how easy it is to keep the code in your code windows organized.  TRY IT FREE FOR 30 DAYS BY CLICKING HERE.



If you are developing in C# and haven't tried CSharpCompleter, you are wasting valuable time typing hundreds of braces {} daily needlessly.  Try CSharpCompleter for 30 DAYS FREE.



Ask a Question, or give your feedback on my articles or products by going to the KnowDotNet Forum or by clicking on My Blog.
  

You can also email me directly at les@KnowDotNet.com.



Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing