In fact, I've known it very early in the WinForm implementation of plug-in programming, the principle is very simple, the main realization of the idea is: first set a plug-in interface as a plug-in style and function of the Convention, and then the specific plug-in to implement the plug-in interface, Finally, the host (the application itself) uses reflection to dynamically acquire the type of plug-in interface as a legitimate plug-in, thus completing the dynamic loading and interaction between the host and the plugin. Because the previous period of time has been engaged in the B/s architecture development no time to practice, and just right now the company leaders asked me to my company's original ERP system structure to restructure, our ERP system based on a distributed three-tier architecture, core business logic on the service side, The presentation layer and the business layer adopt the technology of Web-based service to communicate and interact resources, while the presentation layer is mainly composed of multiple parent-child windows of WinForm. From a business and security perspective, our ERP system based on a distributed three-tier architecture is reasonable, and no change, its biggest core problem is in the three layer of the presentation layer, the front also said that the presentation layer is composed of many WinForm parent-child window, and all in one assembly (that is, a project file), Each time a form changes, you need to recompile the entire project, due to too many files, compiling is slow, but also not conducive to teamwork, often the SVN update conflict or update between the team is not timely, resulting in compilation errors and other problems. In order to solve this problem, my first thought with the company's leadership is to split the presentation layer, from an assembly split into multiple assemblies, from a single file structure into a master-slave file structure, so that can greatly reduce the probability of the occurrence of the problem, then how to achieve it? Nature is the topic of this article: the implementation of modular plug-in programming , some people may be puzzled, this modular plug-in programming and plug-in programming is different? There is no difference in principle, as the beginning of this article, the difference is that ordinary plug-in programming is generally based on a single type to judge and operate in a single type, and I here the modular (also can be said to be component) plug-in programming, is to judge in assemblies and passively collect multiple types of plug-ins in the form of a method callback, with the benefit of avoiding the need for each type to be judged to make the operation more efficient. The idea of this modular plug-in programming, I refer to the ASP. NET route registration mechanism, as in the following code:
public static class Webapiconfig {public static void Register (httpconfiguration config) { config. Routes.maphttproute ( name: "Defaultapi", routetemplate: "Api/{controller}/{id}", defaults:new {id = Routeparameter.optional} ); } }
The advantage of this code is that you focus only on the config thing, and nothing else. And I also take advantage of this implementation principle in the code, the specific steps and code are as follows:
1. Create a Class Library project file (PlugIn) that needs to be primarily a specification for modular plug-in programming (i.e., various interfaces and generic classes), and the host and other components must refer to it.
iappcontext: Application Context Object interface, function: used to collect some common information for application prerequisites and share it with all modules of the entire application (including dynamically loaded components)
Using system;using system.collections.generic;using system.linq;using system.text;using System.Threading.Tasks; Using System.windows.forms;namespace plugin{///<summary>///Application Context object interface//role: Used to gather some common information for application prerequisites and share it with the entire application All modules of the program are used (including dynamically loaded components)///Zuowenjun///2016-3-26///</summary> public interface Iappcontext { <summary>///Application name///</summary> string AppName {get;} <summary>///Application Version//</summary> string appversion {get;} <summary>///user login information, here type is string, real project is an entity class///</summary> string Sessionuserinfo { Get }///<summary>//USER login permission information, here type is string, real project is an entity class///</summary> string Permiss Ioninfo {get;} <summary>///Application global cache, the entire application (including dynamically loaded components) can read/write access///</summary> dictionary<string, Object> AppCache {get;} /// <summary>///Application main Interface form, each component can subscribe or get information about the main interface///</summary> form Appformcontainer {get; }///<summary>///Create a plug-in form instance dynamically in the registration list///</summary>//<param name= "FormType" ></param>///<returns></returns> Form createpluginform (Type formtype); <summary>/////Create a plugin form instance dynamically in the registration list///</summary>//<param name= "Formtypename" >& lt;/param>//<returns></returns> Form Createpluginform (string formtypename); }}
icompoent: Component information Describes an interface that describes some of the main information about the component (or module, the current assembly) so that the host (application) can dynamically get to
Using system;using system.collections.generic;using system.linq;using system.text;using System.Threading.Tasks; namespace plugin{ // /<summary>////Component Information Description interface/ //role: Describes some of the main information about the component (or module, the current assembly) so that the application can dynamically get to ///Zuowenjun//2016-3-26// </summary> public interface icompoent { //< Summary>///Component name/// </summary> string Compoentname {get;} <summary>/// component version, can be implemented by component Update/// </summary> string compoentversion {get;} <summary>///The form Type list pre-registered with the application/// </summary> ienumerable<type> formtypes {get;} }}
Icompoentconfig: Component Information Registration interface, function: The application will find the first time from the assembly to implement the class of the interface and call its Compoentregister method, to passively collect information about the component
Using system;using system.collections.generic;using system.linq;using system.text;using System.Threading.Tasks; namespace plugin{//<summary>// component Information Registration interface/// Role: The application will first find the class that implements the interface from the Assembly and invoke its Compoentregister method to passively collect information about the component///Zuowenjun///2016-3-26// </summary> public Interface icompoentconfig { void Compoentregister (Iappcontext context, out Icompoent compoent);} }
compoent: Component Information Description Class (because all the plug-in modules need to implement icompoent, so it is implemented directly and avoids repeated implementation)
Using system;using system.collections.generic;using system.linq;using system.text;using System.Threading.Tasks; Using plugin;using system.windows.forms;namespace plugin{///<summary>//Component Information Description class///Zuowenjun//20 16-3-26//</summary> public class Compoent:icompoent {private list<type> formtypelist = n EW list<type> (); public string Compoentname {get; Private set; } public string Compoentversion {get; Private set; Public ienumerable<type> Formtypes {get {return formtypelist. AsEnumerable (); }} public Compoent (string compoentname, String compoentversion) {this. Compoentname = Compoentname; This. Compoentversion = compoentversion; } public void Addformtypes (params type[] formtypes) {Type TargetformtypE = typeof (Form); foreach (Type formtype in formtypes) {if (Targetformtype.isassignablefrom (formtype) && !formtypelist.contains (FormType)) {Formtypelist.add (formtype); } } } }}
2. The host (main application) needs to refer to the class library above and implement the Iappcontext implementation class at the same time: AppContext
Using plugin;using system;using system.collections.generic;using system.linq;using system.text;using System.threading.tasks;using system.windows.forms;namespace winformplugin{///<summary>///Application Context object class// Zuowenjun//2016-3-26//</summary> public class Appcontext:iappcontext {internal static A Ppcontext current; Internal Dictionary<string, type> appformtypes {get; Set } public string AppName {get; Private set; } public string AppVersion {get; Private set; } public string Sessionuserinfo {get; Private set; } public string Permissioninfo {get; Private set; } public dictionary<string, object> AppCache {get; Private set; } public System.Windows.Forms.Form AppforMcontainer {get; Private set; Public AppContext (String appName, String appversion, String sessionuserinfo, String permissioninfo, Form appformc Ontainer) {this. AppName = AppName; This. appversion = appversion; This. Sessionuserinfo = Sessionuserinfo; This. Permissioninfo = Permissioninfo; This. AppCache = new dictionary<string, object> (); This. Appformcontainer = Appformcontainer; Public System.Windows.Forms.Form Createpluginform (Type formtype) {if (this. Appformtypes.containsvalue (FormType)) {return activator.createinstance (formtype) as Form; } else {throw new ArgumentOutOfRangeException (string. Format ("the form type {0} is not in the registration list for any one of the module component form Types!") ", Formtype.fullname)," FormType "); }} public System.Windows.Forms.Form Createpluginform (string formtypenAME) {Type type = Type.GetType (formtypename); return Createpluginform (type); } }}
After the
Implements Appcontext, it is necessary to instantiate and populate the Appcontext class, which is placed in the load event of the main form (parent form), as follows:
private void Parentform_load (object sender, EventArgs e) {appcontext.current = new AppContext (" 文俊 Plugin Sample Program "," V16.3.26.1 "," admin "," Administrator ", this); appcontext.current.appcache["logindatetime"] = DateTime.Now; appcontext.current.appcache["BaseDir"] = AppDomain.CurrentDomain.BaseDirectory; AppContext.Current.AppFormTypes = new dictionary<string, type> (); Loadcomponents (); Loadmenunodes (); } private void Loadcomponents () {String path = appcontext.current.appcache["BaseDir"] + "com\\"; Type Targetformtype = typeof (Form); foreach (String FilePath in Directory.GetFiles (path, "*.dll")) {var asy = Assembly.loadfile (fil Epath); var configtype = asy. GetTypes (). FirstOrDefault (t = t.getinterface ("icompoentconfig") = null); if (configtype! = null) {IcompoentCompoent=null; var config = (icompoentconfig) activator.createinstance (Configtype); Config. Compoentregister (Appcontext.current,out compoent);//Key here, get the compoent if (compoent! = null) After the component is instantiated {foreach (Type formtype in Compoent. Formtypes)//Add the Conforming form type collection to the appformtypes of Appcontext {if (targetformtype.is Assignablefrom (FormType)) {APPCONTEXT.CURRENT.APPFORMTYPES.ADD ( Formtype.fullname, FormType); }}}}} private void Loadmenunodes () The implementation should be the dynamic creation of the menu item {this.treeView1.Nodes.Clear () from the database and user permissions; var root = this.treeView1.Nodes.Add ("root"); foreach (Var formtype in AppContext.Current.AppFormTypes) {var node = new TreeNode (formtype.key) {Tag = Formtype.value}; Root. Nodes.Add (node); } }
The following is the implementation menu double-click and open the window, the code is as follows:
private void treeView1_NodeMouseDoubleClick (object sender, Treenodemousecli Ckeventargs e) {if (e.node.nodes.count <= 0)//when non-parent node (i.e.: actual function node) {Showchild Form (E.node.tag as Type); }} private void Showchildform (Type formtype) {var childform= application.openforms.cast& Lt Form> (). Singleordefault (F=>f.gettype () ==formtype); if (ChildForm = = null) {ChildForm = AppContext.Current.CreatePlugInForm (FormType); (Form) Activator.CreateInstance (FormType); Childform.mdiparent = this; Childform.name = "ChildForm-" + DateTime.Now.Millisecond.ToString (); Childform.text = Childform.name; Childform.show (); } else {Childform.bringtofront (); Childform.activate (); } }
3. Implement a Plug-in module, create a Class library project (you can first create a Windows Application project, and then change the output type in its properties to: Class Library, This saves you from referencing some form-related components) Com.first, referencing the previous plug-in specification class library (PLUGIN), and implementing the Icompoentconfig interface class: Compoentconfig
Using system;using system.collections.generic;using system.linq;using system.text;using System.Threading.Tasks; Using Plugin;namespace com.first{///<summary>/// component Information Registration class (one icompoentconfig required for each plug-in module)// Zuowenjun//2016-3-26// </summary> public class Compoentconfig:icompoentconfig { public static Iappcontext AppContext; public void Compoentregister (Iappcontext context,out icompoent compoent) { AppContext = context; var compoentinfo = new Compoent ("Com.first", "V16.3.26.1.1"); Compoentinfo.addformtypes (typeof (Form1), typeof (Form2));//Add the form type that you want to use to the pre-registration list compoent = compoentinfo;// Callback Compoent Instance}}}
This three-step is complete with a simple modular plug-in programming framework, Before running, please put the above plug-in Dll (COM.FIRST.DLL) into the debug application directory under the Com directory, the overall effect is as follows: (the main interface around the layout implementation method to see my blog: share in the WinForm to achieve the left and right layout multi-window interface-sequel)
In order to test the interaction between the plug-in and the main application, I first Form1 the first window in the plug-in assembly (Com.first), the main program will not exit normally if Form1 is open, the code is as follows:
private void Form1_Load (object sender, EventArgs e) { CompoentConfig.AppContext.AppFormContainer.FormClosing + = appformcontainer_formclosing; } void Appformcontainer_formclosing (object sender, FormClosingEventArgs e) { MessageBox.Show (Label1. Text + ", I have not closed, do not allow the application to quit! "); E.cancel = true; } private void Form1_formclosed (object sender, Formclosedeventargs e) { CompoentConfig.AppContext.AppFormContainer.FormClosing-= appformcontainer_formclosing; }
The effect is as follows:
The second Test, in the second window FORM2, increases the implementation of the user login information to restrict certain functions (click button) can not be used, the code is as follows:
private void Button1_Click (object sender, EventArgs e) { if (CompoentConfig.AppContext.PermissionInfo.Equals ("User", StringComparison.OrdinalIgnoreCase)) { MessageBox.Show (this. Name); } else { MessageBox.Show ("Sorry," + CompoentConfig.AppContext.SessionUserInfo + "Your permission role is" + CompoentConfig.AppContext.PermissionInfo + ", and the button is accessible only with user rights! "); } }
The effect is as follows:
As the above code is only for demonstration, so there may be imperfect or even wrong place, the purpose of writing this article is to share the realization of ideas, we can also communicate with each other, thank you!
Attach the source code, you can download and test and change, but also welcome a better idea to communicate here.
Winformplugin.zip
Sharing modular plug-in programming under WinForm