Use C# and the .NET Framework to develop your own plugin architecture

来源:互联网 发布:已备案域名出售 编辑:程序博客网 时间:2024/06/08 09:15

Takeaway: Many dynamic applications allow third parties to create plugins that extend the functionality of the application. Zach Smith explains how developers can leverage the .NET Framework and C# to easily create a plugin architecture of their own. A sample solution is also provided that demonstrates the concepts described.

This article is also available as a TechRepublic download.

In the enterprise world you rarely come across any in-house application that will allow plugins to be written and imported without any changes to core application code. I believe one of the reasons for this is that in the past it was difficult to implement the type of architecture that will allow the use of plugins.

Developers were forced to either spend a large amount of time on the architecture design, or figure out a work around to accomplish their goals. As we all know, large amounts of time are not easy to come by in the enterprise world, and it seems most developers were forced to develop workarounds. Microsoft has solved this issue with C# and the .NET Framework. It is now trivial to develop an application that will allow components to be plugged-in dynamically, without any core code changes.

The solution included with this article (see the project files found in the download) demonstrates how to implement a simple plugin architecture. This demonstration solution dynamically executes tests (each test is a plugin) and reports the results. Each test is able to do something different, and tests should be able to be added or removed without any changes to the core test engine functionality. This allows us to add tests to the application without rebuilding the entire application. I have called this solution "SystemTests", and it contains four projects: SharedObjects, TestEngine, TestExamples, and TestApplication.

Many applications that allow plugins have just a few basic layers that enable the plugin functionality, and the SystemTests solution is no different.

The shared objects layer

The shared objects layer is responsible for providing a way for the main application to communicate with the plugins. Both the main application and the plugins reference the shared objects.

The Shared Objects layer is implemented by the SharedObject project. This project contains the following items:

  • ITest.cs – This is an interface that controls how the core application will communicate with the plugins. In this interface you will find the following methods and properties:
    • Methods:
      • Execute(TestEngineState) – Executes the test.
    • Properties:
      • Result – Returns a TestResult object that holds information about the result of a test.
      • ID – Holds the ID of the test.
      • Name – Holds the name of the test.
      • Description – Holds the description of the test.
  • Test.cs – This is a base class that contains very little functionality. This class is a helper class that each test inherits from. After inheriting from this class, the developer writing the test does not have to explicitly write the properties needed to implement the ITest interface.
  • TestEngineState.cs – This class is passed to every test and allows the tests to determine which tests have passed or failed previously. Using this class you could make tests be dependent on one another.
  • TestResult.cs – When a test has finished executing, it populates its Result property with a TestResult object. This class contains several properties that allow the test engine to determine the results of the test:
    • Properties:
      • Message –This property can contain a message why the test passed or failed.
      • Passed – This is a Boolean property that indicates whether or not the test passed. If the test passed, this property will be set to true.

The core or main application layer

The main application layer is where the core functionality for the application is implemented. This layer ties all of the plugins together, does any reporting that is needed, and interfaces with the user or database.

The main application layer is implemented by the TestEngine project, which contains one class – "Engine". It is the job of the Engine class to gather the tests that must be ran and run them, reporting the results back to the calling application through a TestEngineState object.

The two most important methods in the Engine class are LoadConfiguration and CreateTest. These two methods are explained in detail below:

  • LoadConfiguration
    Determines which plugins must be loaded by parsing an XML configuration file. LoadConfiguration is implemented with the code in Listing A, commented here to explain what is happening:

Listing A


  privatevoid LoadConfiguration(string path)
  {
      XmlDocument configuration = newXmlDocument();
      //Load the given XML file into our XML document.
      configuration.Load(path);

      //Get a NodeList of the Test nodes.
      XmlNodeList tests = configuration.SelectNodes("/TestEngine/Tests/Test");

      //Loop through the test nodes and load each test.
      foreach (XmlNode test in tests)
      {
    //Load the enabled attribute to determine if the test is enabled.
    XmlAttribute enabled = test.Attributes["Enabled"];

    if (enabled.Value.ToLower() == "true")
    {
        XmlAttribute assemblyPath = test.Attributes["Assembly"];
        XmlAttribute typeName = test.Attributes["Type"];

        //Call CreateTest to instantiate the test and add it to the
        // Tests property.
        this.Tests.Add(CreateTest(assemblyPath.Value, typeName.Value));
    }
      }
  }
  • CreateTest
    Creates an ITest object from the given assembly path and type name. This method is shown in Listing B with comments to describe the steps required to dynamically instantiate an object from an assembly:

Listing B


  privateITest CreateTest(string assemblyPath, string typeName)
  {
      //Create an ObjectHandle object that will hold an instance of the requested
      // type in the given assembly. The ObjectHandle class is in the
      // System.Remoting namespace.
      ObjectHandle handle = Activator.CreateInstanceFrom(assemblyPath, typeName);

      //Unwrap the object instance into an object class.
      object test = handle.Unwrap();

      //Test to make sure the object implements the ITest interface. Any object
      // that does not implement the ITest interface will cause an exception to
      // be thrown.
      if (test isITest)
    return (ITest)test; //Return the object as an ITest instance.
      else
    thrownewException("This Type does not implement ITest, which is required./nAssembly: " + assemblyPath + "/nType: " + typeName);
  }

The plugin layer

The plugin layer contains the plugins that are setup for the application. Generally, each plugin contained in this layer will implement one or more interfaces from the shared objects layer. This allows the main application layer to communicate with the plugins.

The plugin layer is implemented by the TestExamples project. The functionality within each test can vary from one test to another, and there are no rules on what a test has to do. The only rule is that the test must implement the ITest interface, everything else is up to the test developer.

Listing C is an example of a very simple test, called "MathTest". This test passes if 1 does not equal 2 (1 != 2), and fails if 1 equals 2 (1 == 2):

Listing C


using System;
using System.Collections.Generic;
using System.Text;
using SystemTests.SharedObjects;

namespace SystemTests.TestExamples
{
    publicclassMathTest : Test, ITest
    {
        publicoverridevoid Execute(TestEngineState state)
        {
              //Set the ID for this test.
              this.ID = "MathTest";
              //Set the Name of this test.
               this.Name = "Math Test";
               //Set the description of this test.
               this.Description = "Tests to see if 1 = 2";

               //Instantiate a TestResult object to hold the
                // results of this test.
               TestResult result = newTestResult();

            //Very simple test to see if 1 = 2
            if (1 == 2)
                result.Passed = true;
            else
                result.Passed = false;

            //Set the result.
            this.Result = result;
        }
    }
}

While this test is extremely simple, as I mentioned above it could do anything as long as it implements the ITest interface.

The included solution contains two other sample tests:

  • SuperManTest – Determines whether or not the Superman class implements the ISuperHero interface.
  • WebTest – Determines whether or not http://microsoft.com can be accessed from the local computer (this may fail if you're behind a proxy).

There is one more project in the solution included with this article called "TestApplication". This project is a very simple Windows form application that displays results from the TestEngine.

Apply to your situation

Take a look at the solution included with this articleand think about how this type of plugin functionality could be applied to business problems at your company. Chances are there are at least a couple instances where this type of highly dynamic functionality could be very helpful. Feel free to use the code examples for your own use!

 
原创粉丝点击