Winform开发框架之插件化应用框架实现
支持插件化應用的開發框架能給程序帶來無窮的生命力,也是目前很多系統、程序追求的重要方向之一,插件化的模塊,在遵循一定的接口標準的基礎上,可以實現快速集成,也就是所謂的熱插拔操作,可以無限對已經開發好系統進行擴展,而且不會影響已有的功能,不在需要的模塊,通過修改配置移除即可。我的Winform開發框架一直以來,來源于多年的項目積累以及客戶的反饋,已經具備了眾多很好的特性以及相關的模塊組合,為了更好擁抱變化,提高基于Winform開發框架基礎上開發新系統的效率,以及為框架融入更多好的特性,故此把我的Winform開發框架在原來的基礎上進行擴展,實現基于插件化應用的框架特性。
為了引入插件化的應用框架特點,我在上一篇隨筆《Winform開發框架之權限管理系統的改進》已經對我的通用權限管理系統進行了改進,其中增加了菜單管理模塊就是為了做插件化做準備的,我們通過權限管理系統配置好菜單的相關信息,然后在應用框架中動態加載菜單功能即可實現。這個菜單模塊,是用來配置基于Web開發框架或者Winform開發框架、WCF開發框架的菜單,通過預先的配置,框架程序的動態加載解析,就能實現插件模塊的熱插拔功能了。實際插件化框架的菜單配置界面效果如下所示。
最終在Winform開發框架的程序中,實現基于插件化的應用,如下所示。
先來看看我改造Winform開發框架,最終形成的框架界面效果,然后在逐一進行介紹,整個開發框架的實現過程。
1、框架的項目工程規劃
為了減少框架整體的復雜性以及提高重用,對插件化的應用框架的項目工程進行了劃分,包括“框架基礎界面模塊”、“插件應用框架啟動模塊”、倉庫管理系統模塊業務邏輯、倉庫管理系統模塊窗體界面等幾個部分。前面兩個部分是插件化框架的核心,可以認為是不需要變化的模塊,提供所有插件應用動態創建以及使用的框架支撐;后面兩個是具體的主業務模塊,這里以WInform開發框架中的倉庫管理系統作為主業務模塊,它本身也是插件應用之一,具體的項目工程結構以及說明如下所示。
| 項目名稱 | 項目說明 |
| WHC.Framework.BaseUIDx | ?框架基礎界面模塊,定義窗體界面基類、通用Excel導入模塊、通用高級查詢模塊等 |
| WHC.Framework.StarterDx | ?插件應用框架啟動模塊,集成權限登錄、動態菜單創建、插件應用動態加載、基礎框架功能等 |
| WHC.WareHouseMis | ?倉庫管理系統模塊的業務邏輯 |
| WHC.Framework.WareHouseDx | ??倉庫管理系統模塊的窗體界面 |
從上面的表格說明中,我們可以看到“WHC.Framework.StarterDx”項目工程,是“插件應用框架啟動模塊”,它基本上只和權限管理系統模塊有關聯關系,因為權限系統是框架底層支撐的模塊,包括用戶登錄、菜單管理、權限控制等都需要從權限管理系統中獲取數據,具體的主要業務功能如下所示。
?
?
2、框架的菜單動態加載
?本文第一張圖片里面,介紹了菜單的定義信息,其中包括了圖標的配置,這些圖片為了方便管理,以及插件需要動態添加菜單圖標,我把它放置在了程序目錄的相對路徑下面,如下所示,動態創建菜單的時候,從指定的路徑去獲取圖標并加載即可。
動態加載菜單是指在插件化應用框架啟動,用戶登錄后進入主界面后,在主界面中動態創建相應的菜單(菜單在權限管理系統中進行配置管理),如下代碼所示。
其中是RibbonPageHelper為了方便動態創建菜單而創建的輔助類,部分代碼如下所示。
/// <summary>/// 動態創建RibbonPage和其下面的按鈕項目輔助類/// </summary>public class RibbonPageHelper{private RibbonControl control;public MainForm mainForm;public RibbonPageHelper(MainForm mainForm, ref RibbonControl control){this.mainForm = mainForm;this.control = control;}public void AddPages(){//約定菜單共有3級,第一級為大的類別,第二級為小模塊分組,第三級為具體的菜單List<MenuNodeInfo> menuList = WHC.Security.BLL.BLLFactory<SysMenu>.Instance.GetTree(Portal.gc.SystemType);if (menuList.Count == 0) return;int i = 0;foreach(MenuNodeInfo firstInfo in menuList){//如果沒有菜單的權限,則跳過if (!Portal.gc.HasFunction(firstInfo.FunctionId)) continue;//添加頁面(一級菜單)RibbonPage page = new DevExpress.XtraBars.Ribbon.RibbonPage();page.Text = firstInfo.Name;page.Name = firstInfo.ID;this.control.Pages.Insert(i++, page);if(firstInfo.Children.Count == 0) continue;foreach(MenuNodeInfo secondInfo in firstInfo.Children){//如果沒有菜單的權限,則跳過if (!Portal.gc.HasFunction(secondInfo.FunctionId)) continue;//添加RibbonPageGroup(二級菜單)RibbonPageGroup group = new RibbonPageGroup();group.Text = secondInfo.Name;group.Name = secondInfo.ID;page.Groups.Add(group); if(secondInfo.Children.Count == 0) continue;foreach (MenuNodeInfo thirdInfo in secondInfo.Children){//如果沒有菜單的權限,則跳過if (!Portal.gc.HasFunction(thirdInfo.FunctionId)) continue;//添加功能按鈕(三級菜單)BarButtonItem button = new BarButtonItem();button.PaintStyle = BarItemPaintStyle.CaptionGlyph;button.LargeGlyph = LoadIcon(thirdInfo.Icon);button.Glyph = LoadIcon(thirdInfo.Icon);button.Name = thirdInfo.ID;button.Caption = thirdInfo.Name; ..................group.ItemLinks.Add(button);}}}} ...............菜單為了方便管理,約定分為3級菜單,三個層級的菜單示意圖如下所示。
啟動頂部的選項卡級別為第一級,下面的Ribbon分組為第二級,具體的功能菜單(或者按鈕)為第三級,以上就是通過菜單數據動態創建的菜單界面圖。
3、框架的用戶信息和權限控制
基礎框架需要傳統的登錄進行驗證,登錄成功后,把用戶關聯的具有的權限下載到本地,然后由系統邏輯統一判斷即可。
插件應用框架系統的登錄代碼和普通的差別不大,登錄后把相關信息存儲在框架變量中,如下所示。
private void btLogin_Click(object sender, EventArgs e){ .................try{string ip = NetworkUtil.GetLocalIP();string macAddr = HardwareInfoHelper.GetMacAddress();string loginName = this.cmbzhanhao.Text.Trim();string identity = WHC.Security.BLL.BLLFactory<WHC.Security.BLL.User>.Instance.VerifyUser(loginName, this.tbPass.Text, Portal.gc.SystemType, ip, macAddr);if (!string.IsNullOrEmpty(identity)){UserInfo info = WHC.Security.BLL.BLLFactory<WHC.Security.BLL.User>.Instance.GetUserByName(loginName);if (info != null){#region 獲取用戶的功能列表List<FunctionInfo> list = WHC.Security.BLL.BLLFactory<WHC.Security.BLL.Function>.Instance.GetFunctionsByUser(info.ID, Portal.gc.SystemType);if (list != null && list.Count > 0){foreach (FunctionInfo functionInfo in list){if (!Portal.gc.FunctionDict.ContainsKey(functionInfo.ControlID)){Portal.gc.FunctionDict.Add(functionInfo.ControlID, functionInfo.ControlID);}}}#endregionbLogin = true;Portal.gc.UserInfo = info;Portal.gc.LoginUserInfo = ConvertToLoginUser(info);this.DialogResult = DialogResult.OK;}}else{MessageDxUtil.ShowTips("用戶帳號密碼不正確");this.tbPass.Text = ""; //設置密碼為空 }}catch (Exception err){MessageDxUtil.ShowError(err.Message);}}
為了使框架記錄的權限信息、用戶數據、以及系統的一些配置信息能夠傳遞到每個插件應用的窗體中,設計了一個插件應用界面需要實現的接口,放在了BaseUI項目工程中。
namespace WHC.Framework.BaseUI {/// <summary>/// 父窗體實現的權限控制接口/// </summary>public interface IFunction{/// <summary>/// 初始化權限控制信息/// </summary>void InitFunction(LoginUserInfo userInfo, Dictionary<string, string> functionDict);/// <summary>/// 是否具有訪問指定控制ID的權限/// </summary>/// <param name="controlId">功能控制ID</param>/// <returns></returns>bool HasFunction(string controlId);/// <summary>/// 登陸用戶基礎信息/// </summary>LoginUserInfo LoginUserInfo { get; set; }/// <summary>/// 登錄用戶具有的功能字典集合/// </summary>Dictionary<string, string> FunctionDict { get; set; }/// <summary>/// 應用程序基礎信息/// </summary>AppInfo AppInfo { get; set; }} }然后在BaseUI的項目中,界面基類BaseForm實現這個接口。
namespace WHC.Framework.BaseUI {public partial class BaseForm : DevExpress.XtraEditors.XtraForm, IFunction{public BaseForm(){InitializeComponent();}...................最后,就是我們如何傳遞用戶信息以及權限信息到窗體本身,傳遞到窗體作為其本身的變量后,就可以很方便使用這些關鍵的信息了。
在我們動態加載插件應用的后,我們會創建對應的Form對象,然后轉換為IFunction接口,賦予該接口相關的變量屬性即可實現用戶信息及權限信息的傳遞,如下代碼所示。
Form tableForm = (Form)Activator.CreateInstance(formType);//如果窗體集成了IFunction接口(第一次創建需要設置)IFunction function = tableForm as IFunction;if (function != null){//初始化權限控制信息 function.InitFunction(Portal.gc.LoginUserInfo, Portal.gc.FunctionDict);//記錄程序的相關信息function.AppInfo = new AppInfo(Portal.gc.AppUnit, Portal.gc.AppName, Portal.gc.AppWholeName, Portal.gc.SystemType);}?4、插件應用的動態加載
上面我們說到,只要是實現基于Form的,我們都可以動態創建方式調用顯示插件的界面出來,而如果界面實現了IFucntion的權限控制接口,那么我們就能夠傳遞給它響應的數據,實現更加完善的控制功能。
在第一張關于權限系統的菜單管理圖片中,我們看到了有個Winform的窗體類型的字段,里面就是用來動態構造插件的配置信息,我們主要是用來構造插件的窗體,并傳遞給它相關數據即可,下圖是菜單管理里面的 “Winform窗體類型” 信息的具體內容。
但我們完成菜單的動態創建后,菜單按鈕的響應事件就是觸發動態加載插件的事件。
我們添加菜單的時候,對它的響應事件也做了處理,具體代碼如下所示。
//添加功能按鈕(三級菜單)BarButtonItem button = new BarButtonItem();.................button.Caption = thirdInfo.Name;button.Tag = thirdInfo.WinformType;button.ItemClick += (sender, e) =>{if (button.Tag != null && !string.IsNullOrEmpty(button.Tag.ToString())){LoadPlugInForm(button.Tag.ToString());}else{MessageDxUtil.ShowTips(button.Caption);}};group.ItemLinks.Add(button);單擊事件的響應處理就是動態構建插件應用的事件,其中就是根據“Winform窗體類型”的數據進行解析的。
string dllFullPath = Path.Combine(Application.StartupPath, filePath);Assembly tempAssembly = System.Reflection.Assembly.LoadFrom(dllFullPath);if (tempAssembly != null){Type objType = tempAssembly.GetType(type);if (objType != null){LoadMdiForm(this.mainForm, objType, isShowDialog); }}通過動態創建菜單模塊,動態加載插件應用,以及權限控制等管理,我們就能隔離框架本身和插件應用模塊之間的耦合性關聯,所有后續開發或者別人開發的業務模塊,都可以很方便的通過權限管理系統配置數據、自動更新模塊更新程序應用的方式,把一個高效、易于擴展、動態管理的系統應用弄得豐富多彩,有聲有色。
基于插件化應用框架的Winform開發框架改造,使得今后開發業務系統,只是基于一定的接口協議,開發插件應用即可,整體性的框架本身可以有專門的人員進行維護,提高團隊對業務模塊的橫向切割和快速開發的效率,更好、統一、高效完成企業化應用框架的搭建和使用。
下面的圖形是之前Winform開發框架的相關功能點集合,加上目前框架的“支持插件化框架應用,能快速開發插件、支持動態擴展”的特點,就顯得更加豐富完善了。
總結
以上是生活随笔為你收集整理的Winform开发框架之插件化应用框架实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓应用程序插件化开发框架 -AAP F
- 下一篇: Maven:Maven 入门