Loading Plug-Ins
Listing 4 contains the method
LoadPlugs
. LoadPlugs
is located in HostForm.cs
and is a
private instance method of the HostForm
class. The LoadPlugs
method
uses .NET reflection to load the available plug-in files, validates them as
plug-ins that can be used by the host application, and then adds them to the
host applications tree view. The method goes through several steps:
- By using the
System.IO.Directory
class, the code is able to do a wildcard search for all of the files with a matching.plug
file extension. TheDirectory
class static methodGetFiles
returns an array ofSystem.String
that contains the absolute path of each file in the hard-coded path that matches the pattern. - After retrieving the array, the method next iterates through each file path attempting to load the file into a
System.Reflection.Assembly
instance. The code that attempts to create anAssembly
instance is wrapped in a try block. If the file is not a valid .NET assembly, then an exception is caught and feedback in the form of a message box is provided to the user about the unsuccessful attempt. If more file paths are left to process, the loop is able to continue. - With an assembly loaded, the code next iterates through each accessible type in the assembly to see if it supports the
HostCommon.IPlug
interface. - If the type supports
HostCommon.IPlug
, the code next validates that the type also supports the attributes that have been defined for plug-ins. If any of the attributes are not supported, aHostCommon.PlugNotValidException
is thrown. After the exception is thrown, it is caught and feedback in the form of a message box is provided to the user explaining why the plug-in failed. If more file paths are left to process, the loop is able to continue. - Finally, if the type supports
HostCommon.IPlug
and defines all of the required attributes, it is wrapped in an instance ofPlugTreeNode
. ThePlugTreeNode
instance is then added to the host applications tree view.
Listing 4: The method LoadPlugs
private void LoadPlugs() { string[] files = Directory.GetFiles("Plugs", "*.plug"); foreach(string f in files) { try { Assembly a = Assembly.LoadFrom(f); System.Type[] types = a.GetTypes(); foreach(System.Type type in types) { if(type.GetInterface("IPlug")!=null) { if(type.GetCustomAttributes(typeof(PlugDisplayNameAttribute), false).Length!=1) throw new PlugNotValidException(type, "PlugDisplayNameAttribute is not supported"); if(type.GetCustomAttributes(typeof(PlugDescriptionAttribute), false).Length!=1) throw new PlugNotValidException(type, "PlugDescriptionAttribute is not supported"); _tree.Nodes.Add(new PlugTreeNode(type)); } } } catch(Exception e) { MessageBox.Show(e.Message); } } return; }
Deployment
The primary framework for the example application is deployed as two assemblies. The first assembly is Host.exe
, and it houses the window forms host application. The second assembly is HostCommon.dll
, and it houses all of the types that are used for communication between the host application and plug-ins. For example, the IPlug
interface is deployed in HostCommon.dll
so that it is equally accessible to both the host application and plug-ins. With the two assemblies in place, additional assemblies can be deployed that store the individual plug-ins. These assemblies are deployed to the plugs directory directly below the application path. The EmployeePlug
class is deployed in the Employee.plug
assembly, and the CustomerPlug
class is deployed in the Customer.plug
assembly. The example has taken the liberty of creating its own .plug
file extension for plug-ins. The plug-in assemblies that are deployed are ordinary .NET library assemblies. Usually library assemblies are deployed with a .dll
extension. The special extension has no influence on the runtime, but helps the plug-ins to stand out to users.
Alternative Designs
The design chosen for the example application is not exclusively correct. For example, using attributes is not required to develop a plug-in solution with C#. The services provided by the two defined attributes could also be provided by two property signatures added to the IPlug
interface definition. Attributes were chosen because the name of a plug-in and its description are declarative in nature and fit quite nicely into the attribute model. Of course, using attributes requires more reflection code in the host application. This is a case-by-case design decision that developers will have to make on their own.
Conclusion
The example application is intended to be as thin as possible to help accentuate the communication between a host application and plug-ins. For a production environment, many improvements can be made to make a more useful solution. Some possible additions include:
- Increasing the communication points between the host and the plug-in by adding more method, property, and event signatures to the
IPlug
interface. Additional interaction between the host and plug-ins will allow for plug-ins that can do more things. - Enabling users to explicitly select the plug-ins that are loaded.
Source Code
The complete source code for the sample application is available for download in walchesk.zip.
Notes
[1] Erich Gamma et al. Design Patterns (Addison-Wesley, 1995).
Shawn Patrick Walcheske is a software developer in Phoenix, Arizona. He is a Microsoft Certified Solution Developer and a Sun Certified Programmer for the Java 2 Platform.