`
ihuashao
  • 浏览: 4710792 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

Creating a Plug-In Framework

阅读更多

Roy Osherove

December 2003

Summary: Shows how to add plug-in support to your .NET applications, and provides a framework you can use to add this functionality. (9 printed pages)

Applies to
Microsoft® ASP.NET
Microsoft® Visual Studio .NET 2003

Download the source code for this article.

Contents

Why Do You Need a Plug-In Framework for Your Application?
Step 1. Create a Simple Text Editor
Step 2. Create the Plug-In SDK
Step 3. Creating Your Custom Plug-In
Step 4. Letting Your Application Know About the New Plug-In
Step 5. Parse the Config File Using IConfigurationSectionHandler
Instantiating and Invoking the Plug-Ins
Invoking the Plug-ins
Summary

<!---->Why Do You Need a Plug-In Framework for Your Application?

People usually add plug-in support in their applications for the following reasons:

  • To extend an application's functionality without the need to re-compile and distribute it to customers.
  • To add functionality without requiring access to the original source code.
  • The business rules for the application change frequently, or new rules are added frequently.

In this article, youll build a very simple text editor, composed of only one form. All it will be able to do is to display text in a single textbox in the middle of the form. Once this application is ready, you'll create a simple plug-in and add it to the application. That plug-in will be able to read the text currently in the textbox, parse it for valid e-mail addresses, and return a string containing only those e-mails. You will then put this text inside the text box.

As you can see, there are a number of "unknowns" in this case study:

  • How do you find the plug-in from within the application?
  • How does the plug-in know what text is in the text box?
  • How do you activate this plug-in?

The answers to all of these questions will become clear as we build the solution.

<!---->Step 1. Create a Simple Text Editor

I won't bore you with the details of this. It's all in the source code download: just a simple form showing a lump of text. I'll assume from this moment that you have created this simple application.

<!---->Step 2. Create the Plug-In SDK

Now that you have an application, you will want it to be able to talk with external plug-ins. How do you make this happen?

The solution is for the application to work against a published interface, a set of public members and methods that will be implemented by all custom plug-ins. I'll call this interface IPlugin. From now on, any developer that would like to create a plug-in for your application will have to implement this interface. This interface will be located at a shared library, which both your application and any custom plug-ins will reference.

To define this interface, you need just a little data from your simple plug-in—its name, and a method that would instruct it to perform a generic action based upon the data in your application.

public interface IPlugin
{
   string Name{get;}
   void PerformAction(IPluginContext context);
}

The code is straightforward, but why send an IPluginContext interface to the PerformAction? The reason you send an interface rather than just a string is to allow more flexibility as to what object you will be able to send. Currently, this interface is very simple:

public interface IPluginContext
{
   string CurrentDocumentText{get;set;}
}

Now, all you have to do is implement this interface in one or more objects, and send this to any plug-in to receive a result. In the future this will allow you to change the string of not just a textbox, but any object you like.

<!---->Step 3. Creating Your Custom Plug-In

All you have to do now is:

  • Create a separate class library object.
  • Create a class that implements the IPlugin Interface.
  • Compile that class and place it in the same folder as the main application.
public class EmailPlugin:IPlugin
{
   public EmailPlugin()
   {
   }
   // The single point of entry to our plugin
   // Acepts an IPluginContext object
// which holds the current
   // context of the running editor.
   // It then parses the text found inside the editor
   // and changes it to reflect any 
// email addresses that are found.
   public void PerformAction(IPluginContext context)
   {
      context.CurrentDocumentText=
             ParseEmails(context.CurrentDocumentText);
   }

   // The name of the plugin as it will appear 
   // under the editor's "Plugins" menu
   public string Name
   {
      get
      {
         return "Email Parsing Plugin";
      }
   }

   // Parse the given string for any emails using the Regex Class
   // and return a string containing only email addresses
   private string ParseEmails(string text)
   {
      const string emailPattern= @"\w+@\w+\.\w+((\.\w+)*)?";
      MatchCollection emails = 
               Regex.Matches(text,emailPattern,
               RegexOptions.IgnoreCase);
      StringBuilder emailString = new StringBuilder();
      foreach(Match email in emails)
      {
         emailString.Append(email.Value + Environment.NewLine);
      }

      return emailString.ToString();
   }
}

<!---->Step 4. Letting Your Application Know About the New Plug-In

Once you have compiled your plug-in, how do you let your application know about it?

The solution is simple:

  • Create an application configuration file.
  • Create a section in the config file that lists all the available plug-ins.
  • Create a parser for this config section.

To complete step one, just add an XML file to the main application.

TipName this file App.Config. This way, every time you build your application, Microsoft® Visual Studio .NET will automatically copy this file into the build output folder and rename it to <yourApp>.Config ,saving you the hassle.

Now, the plug-in developer should be able to easily add an entry in the config file to publish each plug-in he creates. Here's how the config file should look:

<configuration>
<configSections>
      <section name="plugins"
            type="Royo.PluggableApp.PluginSectionHandler, PluggableApp"
            />
</configSections>
   <plugins>
      <plugin type="Royo.Plugins.Custom.EmailPlugin, CustomPlugin" />
   </plugins>
</configuration>

Notice the configSections Tag. This tells the application configuration settings that you have a plug-ins section in this config file, and that you have a parser for this section. This parser resides in the class Royo.PluggableApp.PluginSectionHandler, which is in an assembly named PluggableApp. I'll show you the code for this class below.

Next, you have the plug-ins section of the config file, which lists, for every plug-in, the class name and the assembly name in which it resides. You will use this information when you instantiate the plug-in, later on.

Once the config file is done, you have finished one end of the circle. The plug-in is ready to rock, and has published itself to all the necessary channels. All you have to do now is to enable your application to read in this information, and instantiate the published plug-ins according to this information.

<!---->Step 5. Parse the Config File Using IConfigurationSectionHandler

In order to parse out the plug-ins found within the config file of the application, the framework provides a very simple mechanism enabling you to register a specific class as a handler for a specific portion in your config file. You must have a handler for any portion in the file that is not automatically parsed by the framework; otherwise you will get a ConfigurationException thrown.

In order to provide the class that parses the plug-ins section, all you need to do is to implement the System.Configuration.IConfigurationSectionHandler interface. The interface itself is very simple:

public interface IConfigurationSectionHandler
{
   public object Create(object parent, object configContext, System.Xml.XmlNode section);
}

All you have to do is override the Create method in your custom class, and parse the XML node that is provided to you. This XML node, in this case, will be the "Plugins" XML node. Once you have that, you have all the information needed to instantiate the plug-ins for your application.

Your custom class must provide a default constructor, since it is instantiated automatically by the framework at run time, and then the Create method is called on it. Here's the code for the PluginSectionHandler class:

public class PluginSectionHandler:IConfigurationSectionHandler
{
   public PluginSectionHandler()
   {
   }
   // Iterate through all the child nodes
   //   of the XMLNode that was passed in and create instances
   //   of the specified Types by reading the attribite values of the nodes
   //   we use a try/Catch here because some of the nodes
   //   might contain an invalid reference to a plugin type
   public object Create(object parent, 
         object configContext, 
         System.Xml.XmlNode section)
   {
      PluginCollection plugins = new PluginCollection();
      foreach(XmlNode node in section.ChildNodes)
      {
         //Code goes here to instantiate
         //and invoke the plugins
         .
         .
         .
      }
      return plugins;
   }
}

As you can see in the config file mentioned earlier, you provide the data the framework needs in order to handle the plug-ins section using the configSection tag prior to the actual plug-ins tags.

<configuration>
<configSections>
   <section name="plugins"
      type="Royo.PluggableApp.PluginSectionHandler, PluggableApp"
   />
</configSections>
.
.
.

Notice how to specify the class. The string is composed of two sections: the full name of the class (including encapsulating namespaces), comma, the name of the assembly in which this class is located. This is all the framework needs to instantiate a class, and unsurprisingly, this is exactly the information required for any plug-ins to register for your application.

<!---->Instantiating and Invoking the Plug-Ins

Okay, so how would you actually instantiate an instance of a plug-in given the following string?

String ClassName = "Royo.Plugins.MyCustomPlugin, MyCustomPlugin"
IPlugin plugin =  (IPlugin )Activator.CreateInstance(Type.GetType(ClassName));

What's happening here is this: Since your application does not make a direct reference to the assembly of the custom plug-in, you use the System.Activator class. Activator is a special class able to create instances of an object given any number of specific parameters. It can even create instances of objects and return them. If you have ever coded in ASP or Microsoft® Visual Basic®, you might remember the CreateObject() function that was used to instantiate and return objects based on the CLSID of a class. Activator operates on the same idea, yet uses different arguments, and returns a System.Object instance, not a variant.

In this call to Activator, you pass in as a parameter the Type that you want to instantiate. Use the Type.GetType() method to return an instance of a Type that matches the Type of the plug-in. Notice that the Type.GetType() method accepts as a parameter exactly the string that was put inside the plug-ins tag, which describes the name of the class and the assembly it resides in.

Once you have an instance of the plug-in, cast it to an IPlugin interface, and put it inside your plug-in object. A Try-Catch block must be put on this line, since you cannot be sure that the plug-in described there actually exists, or does in fact support the IPlugin interface you need.

Once you have the instance of the plug-in, add it to the ArrayList of your application plug-ins, and move on to the next XML node.

Here's the code from the application:

public object Create(object parent, 
    object configContext, 
    System.Xml.XmlNode section)
{
   //Derived from CollectionBase
   PluginCollection plugins = new PluginCollection();
   foreach(XmlNode node in section.ChildNodes)
   {
      try
      {
         //Use the Activator class's 'CreateInstance' method
         //to try and create an instance of the plugin by
         //passing in the type name specified in the attribute value
         object plugObject = 
                   Activator.CreateInstance(Type.GetType(node.Attributes["type"].Value));

         //Cast this to an IPlugin interface and add to the collection
         IPlugin plugin = (IPlugin)plugObject;
         plugins.Add(plugin);
      }
      catch(Exception e)
      {
         //Catch any exceptions
         //but continue iterating for more plugins
      }
   }
   return plugins;
}

<!---->Invoking the Plug-ins

After all this is done, you can now use the plug-ins. One more thing is missing, though. Remember that IPlugin.PerformAction() requires an argument of type IPluginContext, which holds all the necessary data for the plug-in to do its work. You'll implement a simple class that implements this interface, which you send to the PerformAction() method whenever you call a plug-in. Here's the code for the class:

public interface IPluginContext
{
   string CurrentDocumentText{get;set;}
}

public class EditorContext:IPluginContext
{
   private string m_CurrentText= string.Empty;
   public EditorContext(string CurrentEditorText)
   {
      m_CurrentText = CurrentEditorText;
   }

   public string CurrentDocumentText
   {
get{return m_CurrentText;}
      set{m_CurrentText = value;}
   }
}

Once this class is ready, you can just perform an action on the current editor text like so:

private void ExecutePlugin(IPlugin plugin)
{
   //create a context object to pass to the plugin
   EditorContext context = new EditorContext(txtText.Text);
   
   //The plugin Changes the Text property of the context
   plugin.PerformAction(context);
   txtText.Text= context.CurrentDocumentText;
   }
}

<!---->Summary

You can see that it's pretty simple to support plug-ins in your applications. You just:

  • Create a shared interfaces library.
  • Create custom plug-ins implementing the custom interfaces.
  • Create context arguments to pass to the plug-ins.
  • Create a section in your config file to hold plug-in names.
  • Instantiate plug-ins using an IConfigurationSectionHandler implementer class.
  • Call your plug-ins.
  • Go home and spend some quality time away from your computer.

About the Author

Roy Osherove has spent the past 5+ years developing data-driven applications for various companies in Israel. He's acquired several MCP titles, written a number of articles on various .NET topics (most of which can be found on his weblog), and loves discovering new things everyday. Roy is also the author of the Feedable service and of the free regular expression tool, The Regulator.

分享到:
评论

相关推荐

    Flex Builder Plug-in and Adobe

    The integration of Flex Builder with Eclipse through the Flex Builder plug-in opens up a wealth of opportunities for developers looking to leverage the strengths of both environments. By following the...

    PL/SQL Developer Plugins

    If you have created a Plug-In that you think is useful for the general PL/SQL Developer community, you can have it included on this page by submitting it to... and we will consider creating a Plug...

    Xamarin in Action: Creating native cross-platform mobile apps

    《Xamarin in Action: Creating Native Cross-Platform Mobile Apps》是一本深入探讨使用Xamarin技术构建原生跨平台移动应用的专业书籍。Xamarin是Microsoft开发的一个强大的框架,它允许开发者使用C#语言和.NET ...

    Creating-A-Simple-Search-Engine-In-PHP.zip_Creating_search_searc

    在"Creating A Simple Search Engine In PHP.html"文件中,详细步骤和代码示例应该会进一步解释这些概念,并指导你如何实际操作。通过实践这个项目,你不仅可以掌握PHP基础,还能深入了解搜索引擎的工作原理,这对...

    creating-a-local-group.zip_Creating_powershell

    "Creating a Local Group.zip"这个压缩包文件显然与使用PowerShell在本地系统上创建用户组有关。让我们详细探讨一下如何使用PowerShell来创建本地组,以及相关的知识点。 首先,我们需要了解PowerShell的基本概念。...

    1-Creating-a-database.rar_Creating

    "Creating-a-database.rar_Creating"这个标题暗示了我们将讨论数据库的创建过程,而"proram source create database ssl server."的描述可能是指创建带有SSL(Secure Sockets Layer)支持的数据库服务器。...

    Manning - Eclipse In Action.pdf

    - **Creating Custom Plug-ins**: Step-by-step guide on how to create custom plug-ins using the Eclipse Plug-in Development Environment (PDE). - **Sample Plug-ins**: Examples of simple plug-ins to ...

    creating-a-graphical-interface-.zip_Creating

    总之,"Creating a Graphical Interface Applications"这一主题涵盖了Java Swing库的基本用法,通过学习这个教程,初学者将能够掌握GUI设计的基本概念,为开发复杂的桌面应用打下坚实的基础。记住,动手实践是学习的...

    Creating-2D-laser-slam-from-scratch

    # Creating-2D-laser-slam-from-scratch ## 这个项目的目的 本人看了几套开源激光SLAM的代码, 依然感觉自己不是特别会, 对SLAM只能说懂个大概, 不敢说理解的特别深入. 同时, 加了一些社群, 发现很多初学激光SLAM的...

    「区块链」grc-w04-creating-order-from-chaos-metrics-that-matter -

    「区块链」grc-w04-creating-order-from-chaos-metrics-that-matter - 数据安全 漏洞挖掘 安全开发 安全分析 安全漏洞 NGFW

    3-Creating-a-schema.rar_Creating

    "Creating a schema" 是数据库设计的重要步骤,它涉及到构建数据结构的蓝图,为存储和组织数据提供框架。本主题主要关注如何在SQL Server 2008中创建和管理模式。 模式(Schema)在SQL Server中是一个逻辑概念,它...

    creating-a-local-user-account.zip_Creating_powershell_创建本地用户

    6. **脚本自动化**:在上述的"creating a local user account.ps1"文件中,很可能包含了创建本地用户账户的完整脚本,便于批量创建或在无人值守环境中使用。 总之,通过PowerShell,我们可以方便地管理Windows系统...

    Creating-maps-in-R:R中地理信息的图形显示入门教程

    在R中可视化空间数据的简介 前言 本教程是基于sp类系统的可视化和分析R中空间数据的简介。 有关最新sf软件包的指南,请查看开发中的书,其源代码可在上找到。 尽管sf在很多方面都取代了sp ,但是学习本教程中的内容...

    Creating Games In C++ - A Step By Step Guide (2007).chm

    Creating Games In C++ - A Step By Step Guide (2007).chm

    Xamarin in Action Creating native cross-platform mobile apps 无水印原版pdf

    Xamarin in Action Creating native cross-platform mobile apps 英文无水印原版pdf pdf所有页面使用FoxitReader、PDF-XChangeViewer、SumatraPDF和Firefox测试都可以打开 本资源转载自网络,如有侵权,请联系上传者...

    Custom Field component with validation for creating easier form-like UI from interface builder.zip

    Custom Field component with validation for creating easier form-like UI from interface builder.zip,Custom Field component with validation for creating easier form-like UI from interface builder.

    Oracle Hyperion Essbase - Creating Multidimensional Databases - 培训资料

    Oracle Hyperion Essbase - Creating Multidimensional Databases - 培训资料 At the end of this lesson, you should be able to: Describe the Essbase database design process Build an Essbase application ...

Global site tag (gtag.js) - Google Analytics