Modular plug-in programming and winform modularization under winform
In fact, I have learned a long time ago about implementing plug-in programming in winform. The principle is very simple. The main implementation idea is: first set a plug-in interface as the plug-in style and function conventions, then the specific plug-in implements this plug-in interface. Finally, the host (application itself) uses reflection to dynamically obtain the type of plug-in interface implemented as a valid plug-in, this completes dynamic loading and interaction between the host and the plug-in. Because I have been engaged in B/S architecture development for a while before, but now the company's leaders have asked me to restructure my company's original ERP system architecture, our ERP system adopts a distributed three-tier architecture. The core business logic is placed on the server. The presentation layer and the business layer use WEB services and other technologies for communication and interaction resources, the presentation layer is mainly composed of multiple Parent and Child windows of WINFORM. From the business and security point of view, our ERP system is reasonable based on the distributed three-tier architecture and does not need to be changed. Its biggest core problem is the presentation layer on the three-tier architecture, as mentioned above, the presentation layer consists of many WINFORM Parent and Child windows, all of which are in the same assembly (that is, a project file). Each time a form is changed, the whole project needs to be re-compiled. Because there are too many files, compilation is slow, and it is not conducive to team cooperation. SVN update conflicts or updates between teams are often not timely, causes compilation errors and other problems. To solve this problem, I first thought of splitting the presentation layer from one assembly to multiple Assembly, from a single file structure to a Master/Slave file structure, in this way, the probability of the above problems can be greatly reduced. How can this problem be achieved? Naturally, it is the topic of this article:Implement modular plug-in programmingIs there a difference between modular plug-in programming and plug-in programming? There is no difference in principle. The difference is that General Plug-in programming is generally based on a single type for judgment and operations of a single type, the modular (or componentized) Plug-in programming here is based on a set of programs for judging and passively collecting multiple types that comply with the plug-in through method callback, the advantage is that you do not need to make judgments for each type to achieve high operation efficiency. This idea of modular plug-in programming, I refer to the ASP. NET routing registration mechanism, 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 only need to pay attention to config, and you do not need to worry about anything else. I also used this implementation principle in the Code. The specific steps and Code are as follows:
1. create a class library project file (PlugIn). This class library must mainly implement modular plug-in programming specifications (that is, various interfaces and general classes ), at that time, the host and other components must reference it.
IAppContext: Application context object interface. function: used to collect necessary public information of an application and share it with all modules of the 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 // function: used to collect necessary public information for the application and share it with all modules of the application (including Dynamically Loaded components) // Author: zuowenjun // 2016-3-26 // </summary> public interface IAppContext {// <summary> // Application name /// </summary> string AppName {get;} /// <summary> /// application version /// </summary> string AppVersion {get;} /// <summary> // user logon information, here the type is STRING, which is an entity class in the real project. /// </summary> string SessionUserInfo {get ;}/// <summary> // The user logon permission information, here the type is STRING. In a real project, it is an object class /// </summary> string PermissionInfo {get ;}/// <summary> /// global cache of the application, read/write access to the entire application (including Dynamically Loaded components) /// </summary> Dictionary <string, object> AppCache {get ;} /// <summary> /// main interface Form of the application. You can subscribe to or obtain information about the main interface from each component. /// </summary> Form AppFormContainer {get ;} /// <summary> /// dynamically create a plug-in form instance in the registration list /// </summary> /// <param name = "formType"> </param> /// <returns> </returns> Form CreatePlugInForm (Type formType ); /// <summary> /// dynamically create a plug-in form instance in the registration list /// </summary> /// <param name = "formTypeName"> </param> /// <returns> </returns> Form CreatePlugInForm (string formTypeName );}}
ICompoent: Component Information Description Interface, role: describes the main information of this component (or module, that is, the current Assembly), so that the host (Application) can dynamically obtain
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 the main information of the component (or module, that is, the current Assembly, so that the application can dynamically get the // Author: zuowenjun // 2016-3-26 // </summary> public interface ICompoent {// <summary> /// component name /// </summary> string CompoentName {get ;} /// <summary> /// component version, which can be updated by component /// </summary> string CompoentVersion {get ;} /// <summary >/// list of forms pre-registered with the application // </summary> IEnumerable <Type> FormTypes {get ;}}}
ICompoentConfig: component information registration interface. function: the application will immediately find the class implementing this interface from the Assembly and call its CompoentRegister method to passively collect information about this component.
Using System; using System. collections. generic; using System. linq; using System. text; using System. threading. tasks; namespace PlugIn {// <summary> // component information registration interface // function: the application will immediately find the class implementing this interface from the Assembly and call its CompoentRegister method to passively collect information about this component. // Author: zuowenjun // 2016-3-26 // </summary> public interface ICompoentConfig {void CompoentRegister (IAppContext context, out ICompoent compoent );}}
Compoent: Component Information Description class (because all subsequent plug-in modules need to implement ICompoent, it is implemented in a unified manner here to avoid 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 // Author: Zuowenjun // /// </summary> public class Compoent: ICompoent {private List <Type> formTypeList = new List <Type> (); public string CompoentName {get; private set;} public string CompoentV Ersion {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. isA SsignableFrom (formType )&&! FormTypeList. Contains (formType) {formTypeList. Add (formType );}}}}}
2. The host (main application) must reference the above class library and implement the IAppContext implementation class at the same time:AppContext
Using PlugIn; using System. collections. generic; using System. linq; using System. text; using System. threading. tasks; using System. windows. forms; namespace WinFormPlugin {// <summary> // context object class of the application // Author: Zuowenjun // 2016-3-26 // </summary> public class AppContext: IAppContext {internal static AppContext 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, strin G permissionInfo, Form appFormContainer) {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 Activato R. CreateInstance (formType) as Form;} else {throw new ArgumentOutOfRangeException (string. Format ("this Form type {0} is not in the Form type registration list of any module component! ", FormType. fullName), "formType") ;}} public System. windows. forms. form CreatePlugInForm (string formTypeName) {Type type = Type. getType (formTypeName); return CreatePlugInForm (type );}}}
After AppContext is implemented, the AppContext class needs to be instantiated and filled. The instantiation process 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 ("", "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 ();} pri Vate 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 (filePath); var configType = asy. getTypes (). firstOrDefault (t => t. getInterface ("ICompoentConfig ")! = Null); if (configType! = Null) {ICompoent compoent = null; var config = (ICompoentConfig) Activator. createInstance (configType); config. compoentRegister (AppContext. current, out compoent); // The key point is here to get the compoent if (compoent! = Null) {foreach (Type formType in compoent. formTypes) // Add a set of conforming form types to AppFormTypes of AppContext {if (targetFormType. isAssignableFrom (formType) {AppContext. current. appFormTypes. add (formType. fullName, formType) ;}}}} private void LoadMenuNodes () // The implementation should be to dynamically create menu items {this. treeView1.Nodes. clear (); 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 );}}
Double-click the menu and open the window. The Code is as follows:
Private void treeView1_NodeMouseDoubleClick (object sender, TreeNodeMouseClickEventArgs e) {if (e. node. nodes. count <= 0) // when a non-parent node (that is, the actual functional node) {ShowChildForm (e. node. tag as Type) ;}} private void ShowChildForm (Type formType) {var childForm = Application. openForms. cast <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 and 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, in this way, you do not need to reference some FORM-related components.) Com. first, reference the previous plug-in specification class library (PlugIn) and implement the class of the ICompoentConfig interface: 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 (Each plug-in module must implement an ICompoentConfig) // Author: 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 types that are considered necessary to the pre-registration list compoent = compoentInfo; // return the Compoent instance }}}
This completes a simple modular plug-in programming framework. Before running the plug-in DLL (Com. first. dll) in the com directory under the debug application directory, the overall effect is as follows: (the layout implementation method of the main interface is visible to my blog: share the multiple window la s on left and right in winform-continuation)
To test the interaction between the plug-in and the main application, I first. first) in the First window Form1, add implementation if Form1 is open, then the main program will not exit normally, 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 it yet. You cannot exit the application! "); E. Cancel = true;} private void form=formclosed (object sender, FormClosedEventArgs e) {CompoentConfig. AppContext. AppFormContainer. FormClosing-= AppFormContainer_FormClosing ;}
The results are as follows:
In the second test, in the second window Form2, the added implementation restricts certain functions (click the button) to be unavailable based on the user login information. The Code is as follows:
Private void button#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 + ", which can be accessed only with the user permission! ");}}
The results are as follows:
The above code is only for demonstration, so there may be imperfections or even errors. The purpose of writing this article is to share the Implementation ideas. You can also exchange ideas with each other. Thank you!
The source code can be downloaded, tested, and modified. You are also welcome to share your ideas here.
WinFormPlugin.zip