Thread: System.plugin

Page 1 of 2 12 Last
  • Jump to page:
    #1
  1. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511

    System.plugin


    I'm looking for some good guides on getting started with system.plugin. System.plugin is worthless as a search term at google or msdn- too much noise because the '.' is ignored.

    Actually, I could use any advice on setting up an extensible system. Here's the full situation:

    We currently have is an old program written with Visual C++ 6. The program processes files from a number of different clients and facilities for insert into a database. The format of each file is at the discretion of the client or facility, as long as it doesn't change without notice once things are initially set up, it contains certain minimum information, and can be easily converted to plain text. We'll do some custom programming to handle whatever they want to send.

    To facilitate this, the current program provides some base classes. For each different file type/facility we have an inherited class specific to that type/facility. The program must also be edited manually to include that file type in the menu and batch run.

    So right now there is a set of .h/.cpp files for each facility that are directly built into the program, whose purpose is to parse a specific kind of file into a common class in memory. The program then knows what to do with instances of the common class.

    We're updating this application to .Net to support some new facilities and enable some new features. For the .Net version I'm planning on doing a very simple translation of the common class, but then first defining an interface that the parser for each format will implement. I may have a couple base classes available to inherit for cases with similar formats, as well, but the main thing will be implementing the interface.

    Now here's where it gets real tricky. I want the code for each file format to exist in it's own class library, and use a human-readable config file (hopefully app.config) to tell my program about what file-types exist and which assembly contains the implementation of the parser for that type. This way each set of custom programming lives in it's own project in visual studio, and we don't have to re-compile and redeploy the main application every time there's a change in one format.

    I'm assuming System.Plugin is the way to go here, but I can't find any good information to get me started with it, and I could be way out in right field anyway.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  2. #2
  3. Arcane Scribbler
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Jun 2005
    Location
    Indianapolis, IN
    Posts
    1,907
    Rep Power
    585
    Actually, I think you're talking about System.AddIn, but that's .NET 3.5. I haven't messed with that part yet.

    However, I can show you some brief stuff on doing plugins using reflection, since I did that in a recent project. You won't even need a config file. Each plugin could define what file-types it should be used for, however you tell the file-types apart. If each file-type has a different extension, the plugin host could look up the plugin in a dictionary.

    I'm going to go put together a brief tutorial now...
    Joel B Fant
    "An element of conflict in any discussion is a very good thing. Shows everybody's taking part, nobody left out. I like that."

    .NET Must-Haves
    Tools: Reflector
    References: Threading in .NET
  4. #3
  5. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    Lol, no wonder I couldn't find anything...

    I had just run across it on a forum somewhere and for some reason it got stuck in my head a plugin intead of addin.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  6. #4
  7. Arcane Scribbler
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Jun 2005
    Location
    Indianapolis, IN
    Posts
    1,907
    Rep Power
    585
    (Let me know if I need to make any corrections.)

    Since this is based on reflection, you'll need some custom attributes. For my purposes, the plugin architecture could be very simple, so I just had two.

    First, I had an attribute to mark an entire assembly as containing one or more plugin classes:
    csharp Code:
    [AttributeUsage( AttributeTargets.Assembly, AllowMultiple=false, Inherited=false )]
    public class PluginAssemblyAttribute : Attribute {
    }
    I used this attribute to check just an assembly so that I didn't have to enumerate the assembly's types and check the attributes of each one of those.

    Then I also had a PluginClassAttribute that contained information specific to identifying a plugin. It, of course, had AttributeTargets.Class only, but AllowMultiple and Inherited were both false. It also had whatever other constructor parameters, properties, etc. that I wanted.

    I also had an interface that each plugin would have to implement. This interface is absolutely necessary, because otherwise you have no way of using a plugin object because it's a plugin and you don't know its interface. (I'm going to lightly sprinkle your problem-domain in here. Very lightly.)

    csharp Code:
    public interface IProcessorThingy {
    	bool CanHandle( Criteria crit );
    	void ProcessStuff( State stuff );
    }
    So now you have your assembly-level attribute and your class-level plugin attribute and your plugin interface. Now you need a plugin host.

    The host needs to gather plugins of a particular type. It needs to maintain a list of all plugin objects of that type. It also needs to handle requests for plugins and distribute the appropriate ones.

    I chose to make mine a Singleton. I also had a public GatherPlugins() method that could be called once from Main().

    The process for gathering the actual plugins, however, is where most of the reflection is. You'll see below (at the bottom, look for Optional) that I made a class for gathering a string[] of assemblies to load. I called this with Scanner.GetValidAssemblies( pluginpath ), but if you don't need to do that, you can just get a list of "*.dll" files.

    Either way, for each assembly, I did something a bit like this:
    csharp Code:
    foreach ( string pluginfile in pluginfiles ) {
    	Assembly asm = Assembly.LoadFile( pluginfile );
     
    	Type[] candidates = asm.GetTypes();
     
    	foreach (Type candidate in candidates) {
    		PluginClassAttribute[] atts = (PluginClassAttribute[]) candidate.GetCustomAttributes( typeof( PluginClassAttribute ), false );
     
    		if ( atts != null && atts.Length > 0 ) {
    			// atts[0] is your attribute object
     
    			// try to create an instance
    			IProcessorThingy thingy = Activator.CreateInstance( candidate ) as IProcessorThingy;
     
    			// if not actually an IProcessorThingy, it'll be null
    			if ( thingy != null ) {
    				// now you can add it to the host's plugin list.
    				// or, add a class that keeps the identifying information
    				//   from the attribute, as well as the plugin object.
    			}
    		}
    	}
    }
    Then the PluginHost object can also have whatever methods you want for retrieving plugins. If the only thing you need to tell different file types appart is their extension, then the extension of the file type that a plugin can be used on might be part of it's PluginClassAttribute information and you could store the plugins in a Dictionary<string,IProcessorThingy>.

    However, that might not stay that way, so it's easier to have a Criteria object that encapsulates information about the file type itself, and then do something like this:

    csharp Code:
    public IProcessorThingy GetProcessorThingy( Criteria crit ) {
    	foreach ( IProcessorThingy thingy in myListOfThingies ) {
    		if ( thingy.CanHandle( crit ) ) {
    			return thingy;
    		}
    	}
     
    	return null;
    }
    A method similar to this will hold up better to versioned file types. For example, let's say somebody puts out a file type with version data on the first line. Then they change their format a bit and the new files have new version data. You need to create a new plugin, and now that's two (or more) plugins for the same file extension. Oops, the dictionary won't work at all anymore.

    To make this more elegant, you can include prioritizing information in the PluginClassAttribute so that they are iterated in GetProcessorThingy() a specific order.

    Personally, I don't think Criteria should be anything other than the file itself or the path to the file. That way you let the IProcessorThingy look at the extension alone, read version information in the file, analyze the file structure,... whatever.

    Optional
    If you want to scan assemblies/types/attributes to make sure it's safe to load the assembly, then you'll need to create an object that does it from withing a new AppDomain. Here's an example:
    csharp Code:
    class Scanner : MarshalByRefObject {
     
    	static public string[] GetValidAssemblies( string path ) {
    		AppDomain sandbox = AppDomain.CreateDomain( "Sandbox" );
     
    		try {
    			Type scantype = typeof( Scanner );
    			ObjectHandle h = sandbox.CreateInstance( scantype.Assembly.FullName, scantype.FullName );
    			Scanner scanner = h.Unwrap() as Scanner;
     
    			return scanner.GatherValidAssemblies( path );
    		}
    		finally {
    			AppDomain.Unload( sandbox );
    		}
     
    		return null; // or new string[0];
    	}
     
    	private string[] GatherValidAssemblies( string path ) {
    		// now executing inside sandbox AppDomain
     
    		List<string> goodfiles = new List<string>();
     
    		string[] files = Directory.GetFiles( path, "*.dll" );
     
    		foreach ( string file in files ) {
    			try {
    				Assembly asm = Assembly.LoadFile( file );
     
    				object[] attributes = asm.GetCustomAttributes(
    					typeof( PluginAssemblyAttribute ), false);
     
    				// naive check. very naive.
    				if ( attributes != null && attributes.Length > 0 ) {
    					goodfiles.Add( file );
    			}
    			catch ( Exception ex ) {
    				// couldn't load assembly
    			}
    		}
     
    		return goodfiles.ToArray();
    	}
    }

    Comments on this post

    • f'lar agrees : awesome
    Joel B Fant
    "An element of conflict in any discussion is a very good thing. Shows everybody's taking part, nobody left out. I like that."

    .NET Must-Haves
    Tools: Reflector
    References: Threading in .NET
  8. #5
  9. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    This is really cool. Thanks! I'll put it to good use.

    My situation is even easier than what you showed. Some 'interfaces', as we call them, have the same file name, but our current code actually has to go out and get (download, decrypt, maybe also unzip or convert from pdf or rtf) the files in most cases, and they all go into their own folder. So part of my interface will be a method to retrieve new files and the location to place the retrieved files- no need for criteria
    Last edited by f'lar; May 22nd, 2008 at 04:31 PM.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  10. #6
  11. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    Hey, this is working great, but I have one additional related question.

    I adapted the optional code at the bottom to my situation, and part of that is I have actually two paths where these will be located (the policy here is that pretty much everything has a main location and an _Dev location next to it). That's already done and works just fine as well- it just iterates through a paths[] array.

    I want to go one step farther to make it easier on the other programmers and add the /bin/debug folder for every project in the current solution to the array, if that's even possible. This way, when we add a new client or facility and create a new class library, the new code will be picked up for testing automatically.
    Last edited by f'lar; May 30th, 2008 at 11:47 AM.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  12. #7
  13. Arcane Scribbler
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Jun 2005
    Location
    Indianapolis, IN
    Posts
    1,907
    Rep Power
    585
    Originally Posted by f'lar
    ...add the /bin/debug folder for every project in the current solution to the array...
    That won't work because the solution/project relationship is not the same relationship as the resulting assemblies.

    But, I know what you're talking about. I had to solve the same problem with my plugin-based project, so I created a Release directory and a Debug directory and changed all projects to build to the correct subdirectory beneath those. So for the Debug directory, the main exe and core library assemblies built to F:\solution\build_Debug\, and all the plugin projects would build to F:\solution\build_Debug\Plugins\.

    That way I could even do a batch build of all configurations of all projects, and I would have a build directory for each configuration, with every assembly in its proper place.
    Joel B Fant
    "An element of conflict in any discussion is a very good thing. Shows everybody's taking part, nobody left out. I like that."

    .NET Must-Haves
    Tools: Reflector
    References: Threading in .NET
  14. #8
  15. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    Thanks again. Not exactly what I wanted to hear, but I about what I expected I'd have to do.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  16. #9
  17. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    Final solution: the documentation I'm writing to go with it will include instructions on changing the build path for the specific desired projects to the appropriate folder. And since this folder is a network share I'm also including instructions on changing the trust level there. What a pain!
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  18. #10
  19. Arcane Scribbler
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Jun 2005
    Location
    Indianapolis, IN
    Posts
    1,907
    Rep Power
    585
    I'd end up looking at ways to automate it. App/script/macro/template/etc.

    Edit: The project files are XML. The output paths are pretty easy to find and edit. The fewer steps one has to perform to create a new plugin project, the less likely a mistake will be.
    Last edited by LyonHaert; June 3rd, 2008 at 12:55 PM.
    Joel B Fant
    "An element of conflict in any discussion is a very good thing. Shows everybody's taking part, nobody left out. I like that."

    .NET Must-Haves
    Tools: Reflector
    References: Threading in .NET
  20. #11
  21. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    I hate to bring up an old thread, but I have one more question:

    Does this have to be an interface, or could I use an abstract base class? The more I look at this the more I want to use an abstract class.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  22. #12
  23. Arcane Scribbler
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Jun 2005
    Location
    Indianapolis, IN
    Posts
    1,907
    Rep Power
    585
    Abstract classes implies a class hierarchy. Interfaces do not. You can implement multiple interfaces, but only inherit from one base class.

    I use multiple interfaces in the plugin stuff. There's one for the basic detection and registering it with the plugin host object. It's a very general interface that is matched with the interface that every different plugin host class implements.

    Then there's the interface that's specific to the type of plugin it is.

    You could use abstract classes if you want, but I recommend making sure its the right way to express the design. Usually they're used when there is common functionality that you want to include in every subclass with few exceptions. If everything in it is abstract, then it's really just a glorified interface.

    If you do use abstract classes, I still recommend designing interfaces at the top, and then have the abstract class implement whatever interfaces.

    Edit:
    A little more on my thoughts on abstract classes. Because base classes tie you to whatever implementation you're inheriting, I prefer to design with certain ideas about what parts govern what subset of all possible subclasses. I think of the interfaces at the top, governing the entire set. I think of base classes as governing only a specialized niche.

    The problem stems from the fact that subclasses cannot control what they inherit from a base class. So one of the questions to ask is, "What happens to this design if the implementation in the base class changes?" (Granted, if the implementation needs to be changed that much and changing an existing class is the only way, then the design is way off...)
    Last edited by LyonHaert; August 6th, 2008 at 09:22 AM.
    Joel B Fant
    "An element of conflict in any discussion is a very good thing. Shows everybody's taking part, nobody left out. I like that."

    .NET Must-Haves
    Tools: Reflector
    References: Threading in .NET
  24. #13
  25. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    The issue comes up because I'm considering what happens to my plugins if I forget something in my interface and need to add an additional hook to the interface late in the game. With an interface, I'll need to change every single plugin. With an abstract class I could add it as a virtual member, with the default implementation that has obviously worked well for every other plugin to date.

    I am taking the thought about having an interface for the bare minimum required of a plugin to heart, and because of this also thinking about breaking my current plugin up into two or three logically separate parts so, a plugin assembly will contain at minimum 3 classes instead of 1, to handle different parts of the process. For example, the code the gathers new files to be processed from the client will be in a separate class from the code that actually processes the files. Haven't decided for sure if I want to go back and do that, though.

    If I do, now is the time while I'm having to go revise some things anyway.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  26. #14
  27. Arcane Scribbler
    Devshed Intermediate (1500 - 1999 posts)

    Join Date
    Jun 2005
    Location
    Indianapolis, IN
    Posts
    1,907
    Rep Power
    585
    Originally Posted by f'lar
    The issue comes up because I'm considering what happens to my plugins if I forget something in my interface and need to add an additional hook to the interface late in the game. With an interface, I'll need to change every single plugin. With an abstract class I could add it as a virtual member, with the default implementation that has obviously worked well for every other plugin to date.
    Sounds good to me.

    For reference, don't forget that an interface can inherit another interface. Here's an example from some of my own work:
    [code=C#]public interface IReportDependencies {
    IParameterDefinition[] RequiredParameters { get; }
    SummaryTable[] RequiredSummaries { get; }
    }

    public interface IReportPlugin : IReportDependencies {
    RenderInfo RunReport( UserParameters parameters );
    }

    public interface IReportPluginByRevenue : IReportPlugin {
    RevenueReportStyle RevenueReportStyle { get; }
    RenderInfo RunReport( UserParameters parameters, RevenueType revtype );
    }[/code]IReportDependencies is separate because there are a couple classes that aggregate IReportPlugin objects that need to aggregate combined sets of those two arrays from all the objects. Some specific conditional branches are used if the IReportPlugin passed to the report renderer can be cast to IReportPluginByRevenue.
    Joel B Fant
    "An element of conflict in any discussion is a very good thing. Shows everybody's taking part, nobody left out. I like that."

    .NET Must-Haves
    Tools: Reflector
    References: Threading in .NET
  28. #15
  29. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    Okay. It's the "if X can be cast to Y" possibility that I hadn't previously considered. That means I could add new functionality to a new interface that inherits from the original and not have to modify the old plugins. Any new plugin that needs to take advantage of it could just implement both interfaces, and the host code can just check the class instance through reflection. Though that might make the host code a little less clear, it will help keep logical areas separate.

    One more follow up question then. Let's say I do split out the processes to retrieve new files from the client and importing the gathered files to the database into separate interfaces. I imagine that most plugins would implement those interfaces in the same class even if it's not strictly required. So how could my host code know to return the same instance when that happens vs separate instances when it doesn't? The alternative of returning two instances of the same class seems unnecessary and wasteful.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
Page 1 of 2 12 Last
  • Jump to page:

IMN logo majestic logo threadwatch logo seochat tools logo