Introduction
I guess I need to start off with the customary, "This is my first CodeProject article, so please be nice." Now that it's been said, let's move on the the meat of this article. The sample project/application was designed to demonstrate the creation of an infrastructure within an application in order to provide some extensibility through plug-ins. This project is hosted on SourceForge. If you wish to contribute to it, provide feedback or alternative approaches, please feel free to contact me. The solution provided, Plug-in, includes five C# projects:
- TaskPluginInterface: The main library.
- TaskPluginTest: An application provided to test the plug-ins.
- FileCopyPlugin: A plug-in created to demonstrate the implementation of ITaskPluginInterface. This plug-in copies one or more files from one directory to another.
- RunCommandPlugin: A plug-in created to demonstrate the implementation of ITaskPluginInterface. This plug-in will run commands using the System.Diagnostics.Process class.
- PluginShellPlugin: This plug-in provides a basic shell for implementing the ITaskPluginInterface.
Technologies used
- Microsoft .NET Framework v2.0, although v1.1 could be used with some modifications.
- System.configuration
- System.Reflection
- System.Collections.Generic
- System.File.IO
- System.Diagnostics
- Delegates, events, and event arguments
- Log4Net
- Guadagno.Utilties.dll, a series of classes that provides common functionality amongst most of my projects. The key class is ConfigurationBase, which is used for the application configuration.
- SchedulerTime.dll, a class library used for the scheduling. See the reference article below.
Quick and dirty: how-to
Create the plug-in host
Step 1: Include a reference to the TaskPluginInterface.dll in your solution.
Step 2: Get the folder location for the plug-ins. I use \Plugins. Call the Utilities.GetPluginFolder() method.
Step 3: Get a list of plug-ins. Call Utilities.GetPlugins(folderName).
Step 4: Call the plug-ins Execute method.
Create the plug-in
Option 1: code from scratch
Step 1: Include a reference to the TaskPluginInterface.dll in your solution.
Step 2: Create a class that inherits from ITaskPlugin.
public class RunCommand : ITaskPlugin
{
}
Step 3: If you are using Visual Studio 2005, you can use the Intellisense or SmartTag to create the stub implementation of ITaskPlugin.
Step 4: Place your code in the Execute method.
Option 2: use the PluginShellPlugin class
Step 1: Rename the class to what ever name you want.
Step 2: Change the resource file strings to accurately describe your plug-in.
Step 3: Change the code in the DoWork() method.
TaskPluginInterface namespace
ITaskPlugin interface
The ITaskPlugin interface defines the methods, events and properties that the plug-ins must implement.
Methods
| Enumeration Name |
Description |
| Execute |
Executes the task |
Properties
| Enumeration Name |
Description |
| Author |
The author of the plug-in. |
| Description |
The description of the plug-in. |
| Extension |
The file extension(s) that this plug-in handles. This is currently not used. |
| Extension Description |
A description of the extension(s) that this plug-in handles. This is currently not used. |
| Name |
The name of the plug-in. |
| Schedule |
The schedule for the plug-in. This was created for a future project, the pluggable task scheduler. |
| Version |
The version of the plug-in. |
Events
| Enumeration Name |
Description |
| EventEnd |
This is used to notify the host about the ending of the Execute method. |
| EventException |
This is used to notify the host about any Exceptions that have occurred. |
| EventProcessing |
This is used to notify the host about the processing of the Execute method. |
| EventStart |
This is used to notify the host about the starting of the Execute method. |
PluginAttribute class
The PluginAttribute class defines the class attributes which can be applied to a class that is implementing the ITaskPlugin interface. This class defines an Attribute, which is PluginType. The PluginType corresponds to the PluginType class. Syntax:
TaskPluginInterface.Plugin(PluginType.Executable)]
public class RunCommand : ITaskPlugin
{
}
Enumerations
ExecuteResult
This enumeration is used as the return value of the Execute method for the ITaskPlugin interface.
| Enumeration Name |
Description |
| Cancelled |
Indicates that the Execute method of the plug-in was cancelled. |
| Exception |
Indicates that an exception was raised by the plug-in. |
| Failed |
Indicates that the plug-in has failed. |
| Ok |
Indicates that everything went OK with execute of the plug-in. |
PluginType
This enumeration is used to indicate the type of plug-in that is being created. Applying the PluginType attribute to a class does not have an effect the functionality of the class. Its only purpose is to provide a classification of the plug-ins available for list boxes or separate functionality.
| Enumeration Name |
Description |
| Executable |
Indicates that the plug-in is used for launching executables. |
| Import |
Indicates that the plug-in is used for importing data. |
| SQL |
Indicates that the plug-in is used for processing SQL statements. |
| Unknown |
Denotes that the type of plug-in is not known. |
PluginEventArgs class
The PluginEventArgs class provides information back to the plug-in host. This information is provided back to the plug-in host via one of the four events. PluginEventArgs properties:
| Name |
Description |
| Cancel |
Indicates if the operation should be canceled. |
| Message |
A message returned from the plug-in. |
| Progress |
Indicates the current progress of the plug-in. The Progress is returned as a struct, which is defined in the Utilities class. |
| RaisedException |
An exception raised from the plug-in. |
ScheduleConfig class
The ScheduleConfig class provides functions that will load the schedule configuration. Note that this was created for a future version of the application.
Utilities class
The Utilities class provides helper functions to interact with and discover plug-ins. All of the provided methods are static, so there is no need to create an instance of the Utilities class. Utilities class methods:
| Method Name |
Description |
| FileContainsPlugins |
Determines if the file contains at least one plug-in. There is one overload that allows you specify a PluginType. |
| GetPlugin |
Returns a plug-in of the specified class from the specified file. |
| GetPluginFileList |
Gets a list files that contain plug-ins within the specified directory. There is one overload that allows you specify a PluginType. |
| GetPluginFolder |
Returns the plug-in directory. |
| GetPlugins |
Gets a List of plug-ins from the specified file. There is one overload that allows you specify a PluginType. |
Included plug-ins
FileCopy
The FileCopy plug-in provides the ability to copy files that fit a mask from one directory to another. This plug-in reads the configuration file, FileCopyPlugin.Settings.xml, for all of the Directory nodes listed in the Directories element. For example:
<Directories>
<Directory FromDir="C:\Temp\" ToDir="d:\Temp\" FileMask="*.*" />
<Directory FromDir="C:\Temp\junk" ToDir="d:\Temp\junk" FileMask="*.txt" />
<Directory FromDir="C:\Temp\junk" ToDir="d:\Temp\junk" FileMask="*.xyz" />
</Directories>
The following configuration contains three copy commands.
- Will copy all of the files located in the c:\temp directory to d:\temp
- Will copy all of the TXT files located in the c:\temp\junk directory to the d:\temp\junk directory
- Will copy all of the XYZ files located in the c:\temp\junk directory to the d:\temp\junk directory
PluginShell
The PluginShell plug-in provides a class that contains everything needed to implement the ITaskPluginInterface.
RunCommand
The RunCommand plug-in provides the ability to execute processes using the System.Diagnostics.Process namespace. This plug-in reads the configuration file. RunCommandPlugin.Settings.xml, for Command elements listed in the Commands node. Each command element has XmlChildNodes that correspond to parameters of the ProcessStartInfo struct of the Process object.
TaskPluginTest
The TaskPluginTest is a sample application that demonstrates the use of the TaskPluginInterface library. This application utilizes the TaskPluginInterface utilities to load the list of application plug-ins. In addition, this application displays the plug-in metadata, executes the plug-in and handles events from the plug-in.
Future enhancements
- Create NUnit test cases for entire library.
- Create VB.NET sample plug-ins.
- Improve on the Schedule library to allow for the displaying of all future schedules.
- Integrate the ITaskPlugin interface into a Windows Service application to provide a flexible version of the Windows Task Scheduler.
References
History
| Version |
Date |
What was done |
| 1.0 |
4/21/07 |
Initial release. |
| 1.0.0.1 |
4/21/07 |
Fixed an issue with spaces in the path. |
| 1.0.0.2 |
5/5/07 |
Added better logging information to the plug-ins and the plug-in host.
Updated the Utilities class to replace the logic for GetPluginFolder.
|
| 1.0.0.2 |
6/12/07 |
Article edited and posted to the main CodeProject.com article base. |
| You must Sign In to use this message board. |
|
|
 |
|
 |
Nice Article, better than some I have read that's for sure!
I am curious: How large would the effort be, in your estimate, to build in the ability for plugins to be hosts and allow other plugins to 'stick'?
I am looking at MEF (Managed Extensibility Framework) and I need to be able to provide 'stickiness' with my addins but I am not keen on using IOC to accomplish it
Thoughts?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I am trying to develop a host with multiple plugins and after I posted my message to you I came upon MEF. I would like to be able to have plugins have plugins which could have plugins (or extensions if that is a better word) and it appears MEF fits the bill.
What kind of success have you had with MEF?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I see. I started using it friday to implement what I described earlier.
Good luck with your codeplex project - it looks extremely interesting. Can I ask what kind of use did you have planned for your projects outputs? I mean, beyond what it says about creating assemblies that can be run at any time.
Tab
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I'm in the process of building an application that parses various text sources. I was planning to use a plugin to encapsulate the parsing mechanisms. I will likely have many textual sources, but only a few plugins. i will need to store the settings for each source and its plugin's setting.
does this seem like an appropriate use of the plugin pattern? it seems like persisting a plugin's settings is a bit odd.
also, i'm a bit troubled by managing instances. assume that i have two plugins. if i use a pluginManager to load the plugins, i will have one instance of each plugin. if i have a hundred text sources, each using one or the other plugin, with unique plugin settings, do i reuse the two loaded plugin instances or do i somehow clone a plugin instance as needed?
thanks for your thoughts.
craig
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I use this "architecture" for import applications. One of the applications I work on gets data imports from multiple sources and I wrote a couple of different plug-ins to accomplish this. In your particular scenario you might need to do things a bit different. Off the top of my head I can see you "writing" different plug-ins for a different file types and having separate configuration files for each of the customized sources. So when your main application starts up you load your plug-ins then you read your individual "import settings". Within your "import settings" file, you should reference what plug-in to use and other plug-in specific settings. As far as managing multiple instances of the plug-ins there is a good article on threading on CodeProject. See Work Queue based multi-threading
I hope this helps. Joseph Guadagno http://www.josephguadagno.net
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
It does.
FYI, the application 'scrapes' HTML to convert it to XML.
The first plugin uses Xslt to do the conversion. It needs the Url(s) to the target page(s), the Url to the Xsl document, and perhaps a list of Xslt parameters, if necessary.
My first thought was to XmlSerialize the plugins settings and save them with the name and description of the 'scrape'. The challenge is to ensure that I can XmlSerialize for all plugins. While I could serialize the plugin itself, doesn't this defeat the idea of using a PluginManager?
While it hasn't been approved, this project will be available at squeeqee.sourceforge.net.
Ultimately, I'd like to use this framework as the basis of a shared webservice. Once someone develops the 'scrape', others would be able to use it in their applications.
Thoughts?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I would think that you would want to "XmlSerialize" the plug-in settings since it is more likely that the setting will change. In addition, the settings would be more manageable.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Does your framework monitor the plugin directory? If a plugin is added/changed or removed, how will your framework respond?
Thanks and good work.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
For those of you who downloaded the earlier version (within the last 12 hours), there was a problem with reading plug-ins when they were in a path that contained spaces. This was fixed and reposted.
Thanks to Gary for helping to debug this.
|
| Sign In·View Thread·PermaLink | 4.20/5 |
|
|
|
 |
|
 |
Hi! I just downloaded and compiled your plugin solution but it doesn't seem to be working. When I load a plugin the three combo boxes seem to work as expected but none of the informational text boxes (i.e. Name, Description, Author, etc.) get filled in. The Execute button is enabled but when I click on it nothing happens. Any ideas?
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Did you change anything or just compiled an ran it. The properties of the three included plug-ins are retrieved from the plug-in resource files. For example: The FileCopyPlugin plugin contains a resource file (located in the Properties folder of the solution) named FileCopyPlugin.resx. This resource file is read by the FileCopyPlugin class implementation of ITaskPlugin and returned to the host. This can be found in lines 97 - 132.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
 |
Same thing with demo code. This really looks useful hope the find a solution. Thanks for your contribution
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The Extension and Extension Desc properties are blank by default. The only properties that should populate are Name, Description, Version and Author.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Are any Exceptions being raised? What happens if you step through the code? Are there any references that are missing?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
In Utilities.GetPlugin:
try { System.Runtime.Remoting.ObjectHandle obj = Activator.CreateInstanceFrom(filename, className); return (ITaskPlugin)obj.Unwrap(); } catch (Exception e) { }
I added the Exception e to catch it here and get:
Exception has been thrown by the target of an invocation.
Compiled under 2005
I believe that if you use the code on the web site you will receive the same.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|