23.11 Creating a Plug-In Architecture

 <  Day Day Up  >  

You want to use reflection to create a plug-in architecture for your application.


Technique

Plug-in architectures are used extensively to add extra functionality to an application after it is built. Because there is no standard way of creating a plug-in architecture, this example merely presents a single way of creating such an architecture using the .NET reflection classes.

The example in this recipe ultimately ends up being a Notepad clone with the ability to extend it using plug-ins. The first step in any plug-in architecture implementation is to define a standard plug-in interface that all plug-ins must implement to successfully integrate with your application. You should create this interface within a class library so the plug-in developer can simply add a reference to the assembly and derive a class from the interface defined within. For the Notepad clone, the plug-in interface is simple enough. The interface contains a single get property named Name that returns the name of the plug-in, which is used as a menu item in the application's main menu. The interface also contains a method named Execute , which accepts a string parameter that contains text from the application. The plug-in method then performs its relevant operation on the text and returns the final result as a string. Here's the interface definition contained within the NotepadPluginSDK class library:

 
 using System; namespace _11_NotepadPluginSDK {     public interface INotepadPlugin     {         string Name         {             get;         }         string Execute( string selection );     } } 

When a plug-in developer wants to create a new plug-in for the application, all he has to do is add a reference to the plug-in software development kit (SDK) assembly and implement the INotepadPlugin interface. The following example shows three different plug-ins contained within the same class library. The implementations aren't anything special because we still aren't dealing with reflection yet. That code is implemented within the Notepad application itself. The three plug-ins are implemented as follows .

Listing 23.8 Implementing Plug-In Interface Methods
 using System; using System.Text; namespace _11_NotepadPlugins {     public class Rot13 : INotepadPlugin     {         public string Name         {             get             {                 return "Rot13";             }         }         public string Execute(string selection)         {             char[] chars = selection.ToCharArray( 0, selection.Length );             for( int i = 0; i < chars.Length; i++ )             {                 if( chars[i] != '\r' && chars[i]!= '\n' )                     chars[i] += (char)13;             }             return new StringBuilder().Append(chars).ToString();         }     }     public class UnRot13 : INotepadPlugin     {         public string Name         {             get             {                 return "UnRot13";             }         }         public string Execute(string selection)         {             char[] chars = selection.ToCharArray( 0, selection.Length );             for( int i = 0; i < chars.Length; i++ )             {                 if( chars[i] != '\r' && chars[i]!= '\n' )                     chars[i] -= (char)13;             }             return new StringBuilder().Append(chars).ToString();         }     }     public class Reverser : INotepadPlugin     {         public string Name         {             get             {                 return "Reverser";             }         }         public string Execute(string selection)         {             char[] chars = selection.ToCharArray();             Array.Reverse(chars);             // fix \r\n reversal             for( int i = 0; i < chars.Length; i++ )             {                 if( chars[i] == '\r' ) chars[i] = '\n';                 else if( chars[i] == '\n' ) chars[i] = '\r';             }             return new StringBuilder().Append(chars).ToString();         }     } } 

The Notepad application itself is a Windows Form “based application containing a main menu and a TextBox control for the content. The main menu contains a File menu, allowing for such operations as creating a new file, opening an existing file, and saving and exiting the application. The Plugins menu is initially empty because we are using late binding to find plug-ins when the application runs.

Within the form constructor, shown in the following code, an ArrayList is created to hold all the plug-in instances as they are created. Next , each DLL assembly located within the application's plugin subdirectory is loaded. Next, each module in the assembly is enumerated to capture any modules within a multifile assembly. Within each module, every defined Type is enumerated using the GetTypes reflection method, and a search is performed on each Type for the INotepadPlugin interface. This step uses the custom searching technique described in Recipe 23.8, "Searching Assemblies Using Custom Search Techniques." If the interface is implemented on a certain Type , the corresponding object is created using the Activator.CreateInstance method, and the return value is cast to the INotepadPlugin interface. At this point, you have an instance of an object that is known to implement the interface, and you are free to call any properties and methods, which is what the next step performs. After the plug-in object instance is added to the ArrayList of plug-ins, the Name property is accessed and added to the Plugins menu item within the main menu. Additionally, an event handler for the menu item is passed in. When the user clicks a certain plug-in menu item, the event handler grabs the corresponding plug-in instance from the ArrayList and calls the Execute method, passing either the selected text if any exists or the entire text of the TextBox .

Listing 23.9 Creating a Text Editor with Plug-in Support
 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.IO; using System.Reflection; using _11_NotepadPluginSDK; namespace _11_NotepadWithPlugins {     public class Form1 : System.Windows.Forms.Form     {         private System.Windows.Forms.MainMenu mainMenu1;         private System.Windows.Forms.MenuItem menuItem1;         private System.Windows.Forms.MenuItem mnuNew;         private System.Windows.Forms.MenuItem mnuOpen;         private System.Windows.Forms.MenuItem mnuSave;         private System.Windows.Forms.MenuItem mnuSaveAs;         private System.Windows.Forms.MenuItem mnuExit;         private System.Windows.Forms.MenuItem mnuPlugins;         private System.Windows.Forms.MenuItem menuItem8;         private System.Windows.Forms.TextBox tbContent;         private System.Windows.Forms.OpenFileDialog openFileDialog1;         private System.Windows.Forms.SaveFileDialog saveFileDialog1;         private System.ComponentModel.Container components = null;         private string fileName = null;         private ArrayList plugins;         public Form1()         {             InitializeComponent();             plugins = new ArrayList();             // enumerate files in plugin directory             foreach( string fileName in Directory.GetFiles(                       Environment.CurrentDirectory + @"\plugins", "*.dll" ))             {                 // load assembly                 Assembly asm = Assembly.LoadFile( fileName );                 // get all modules in assembly                 foreach( Module module in asm.GetModules(false) )                 {                     // for all types                     foreach( Type t in module.GetTypes() )                     {                         // check to see if it supports the                         // INotepadPlugin interface                         foreach (Type iface in t.FindInterfaces(                                   new TypeFilter(PluginInterfaceFilter) ,                                   "INotepadPlugin" ))                         {                             // create plug-in and add to menu                             INotepadPlugin plugin = (                              INotepadPlugin)Activator.CreateInstance(t);                             plugins.Add(plugin);                             mnuPlugins.MenuItems.Add( plugin.Name,                              new EventHandler(mnuPlugin_Click) );                         }                     }                 }             }         }         protected override void Dispose( bool disposing )         {             if( disposing )             {                 if (components != null)                 {                     components.Dispose();                 }             }             base.Dispose( disposing );         }         private void InitializeComponent()         {             this.tbContent = new System.Windows.Forms.TextBox();             this.mainMenu1 = new System.Windows.Forms.MainMenu();             this.menuItem1 = new System.Windows.Forms.MenuItem();             this.mnuNew = new System.Windows.Forms.MenuItem();             this.mnuOpen = new System.Windows.Forms.MenuItem();             this.mnuSave = new System.Windows.Forms.MenuItem();             this.mnuSaveAs = new System.Windows.Forms.MenuItem();             this.menuItem8 = new System.Windows.Forms.MenuItem();             this.mnuExit = new System.Windows.Forms.MenuItem();             this.mnuPlugins = new System.Windows.Forms.MenuItem();             this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();             this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog();             this.SuspendLayout();             // NOTE: Form initialization code removed for brevity             this.ResumeLayout(false);         }         [STAThread]         static void Main()         {             Application.Run(new Form1());         }         // callback used when searching for implemented interfaces         public static bool PluginInterfaceFilter( Type typeObj, object criteria )         {             if( typeObj.Name == (string) criteria )                 return true;             return false;         }         // Called when user clicks on a plug-in menu item         private void mnuPlugin_Click(object sender, System.EventArgs e)         {             MenuItem item = (MenuItem) sender;             INotepadPlugin plugin = (INotepadPlugin) plugins[item.Index];             if( tbContent.SelectionLength > 0 )                 tbContent.SelectedText = plugin.Execute( tbContent.SelectedText );             else                 tbContent.Text = plugin.Execute( tbContent.Text );         }         // the following methods perform normal notepad menu functionality         private void mnuNew_Click(object sender, System.EventArgs e)         {             tbContent.Text = "";             fileName = null;         }         private void mnuOpen_Click(object sender, System.EventArgs e)         {             if( openFileDialog1.ShowDialog() == DialogResult.Cancel )                 return;             tbContent.Text = "";             fileName = openFileDialog1.FileName;             OpenText( fileName );         }         private void mnuSave_Click(object sender, System.EventArgs e)         {             if( fileName == null )             {                 mnuSaveAs_Click( sender, e );                 return;             }             SaveText( fileName );         }         private void mnuSaveAs_Click(object sender, System.EventArgs e)         {             if( saveFileDialog1.ShowDialog() == DialogResult.OK )             {                 SaveText( saveFileDialog1.FileName );                 fileName = saveFileDialog1.FileName;             }         }         private void mnuExit_Click(object sender, System.EventArgs e)         {             this.Close();         }         private void OpenText( string fileName )         {             StreamReader sr = File.OpenText(fileName);             tbContent.Text = sr.ReadToEnd();             sr.Close();         }         private void SaveText( string fileName )         {             StreamWriter sw = File.CreateText(fileName);             sw.Write( tbContent.Text);             sw.Close();         }     } } 

Comments

The example in this recipe tied in several techniques described in this chapter. It demonstrated how to load an assembly from the file system, how to enumerate all the modules and types within that assembly, and how to perform custom searching to find the specific item of interest. Furthermore, the example showed how to dynamically create an object at runtime.

In my opinion, plug-in architectures allow you to create an extremely flexible and versatile application. Although most plug-in architectures are designed to allow third parties to extend the functionality of an application, don't pass up using a plug-in architecture if you believe no one will create plug-ins. In some of our past projects, we created plug-in architectures solely for the purpose of allowing us to extend our own applications without messing around with the internals of the main source code.

 <  Day Day Up  >  


Microsoft Visual C# .Net 2003
Microsoft Visual C *. NET 2003 development skills Daquan
ISBN: 7508427505
EAN: 2147483647
Year: 2003
Pages: 440

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net