|
支持插件化應(yīng)用的開(kāi)發(fā)框架能給程序帶來(lái)無(wú)窮的生命力,也是目前很多系統(tǒng)、程序追求的重要方向之一,插件化的模塊,在遵循一定的接口標(biāo)準(zhǔn)的基礎(chǔ)上,可以實(shí)現(xiàn)快速集成,也就是所謂的熱插拔操作,可以無(wú)限對(duì)已經(jīng)開(kāi)發(fā)好系統(tǒng)進(jìn)行擴(kuò)展,而且不會(huì)影響已有的功能,不在需要的模塊,通過(guò)修改配置移除即可。我的Winform開(kāi)發(fā)框架一直以來(lái),來(lái)源于多年的項(xiàng)目積累以及客戶的反饋,已經(jīng)具備了眾多很好的特性以及相關(guān)的模塊組合,為了更好擁抱變化,提高基于Winform開(kāi)發(fā)框架基礎(chǔ)上開(kāi)發(fā)新系統(tǒng)的效率,以及為框架融入更多好的特性,故此把我的Winform開(kāi)發(fā)框架在原來(lái)的基礎(chǔ)上進(jìn)行擴(kuò)展,實(shí)現(xiàn)基于插件化應(yīng)用的框架特性。 為了引入插件化的應(yīng)用框架特點(diǎn),我在上一篇隨筆《Winform開(kāi)發(fā)框架之權(quán)限管理系統(tǒng)的改進(jìn)》已經(jīng)對(duì)我的通用權(quán)限管理系統(tǒng)進(jìn)行了改進(jìn),其中增加了菜單管理模塊就是為了做插件化做準(zhǔn)備的,我們通過(guò)權(quán)限管理系統(tǒng)配置好菜單的相關(guān)信息,然后在應(yīng)用框架中動(dòng)態(tài)加載菜單功能即可實(shí)現(xiàn)。這個(gè)菜單模塊,是用來(lái)配置基于Web開(kāi)發(fā)框架或者Winform開(kāi)發(fā)框架、WCF開(kāi)發(fā)框架的菜單,通過(guò)預(yù)先的配置,框架程序的動(dòng)態(tài)加載解析,就能實(shí)現(xiàn)插件模塊的熱插拔功能了。實(shí)際插件化框架的菜單配置界面效果如下所示。
最終在Winform開(kāi)發(fā)框架的程序中,實(shí)現(xiàn)基于插件化的應(yīng)用,如下所示。
先來(lái)看看我改造Winform開(kāi)發(fā)框架,最終形成的框架界面效果,然后在逐一進(jìn)行介紹,整個(gè)開(kāi)發(fā)框架的實(shí)現(xiàn)過(guò)程。
1、框架的項(xiàng)目工程規(guī)劃為了減少框架整體的復(fù)雜性以及提高重用,對(duì)插件化的應(yīng)用框架的項(xiàng)目工程進(jìn)行了劃分,包括“框架基礎(chǔ)界面模塊”、“插件應(yīng)用框架啟動(dòng)模塊”、倉(cāng)庫(kù)管理系統(tǒng)模塊業(yè)務(wù)邏輯、倉(cāng)庫(kù)管理系統(tǒng)模塊窗體界面等幾個(gè)部分。前面兩個(gè)部分是插件化框架的核心,可以認(rèn)為是不需要變化的模塊,提供所有插件應(yīng)用動(dòng)態(tài)創(chuàng)建以及使用的框架支撐;后面兩個(gè)是具體的主業(yè)務(wù)模塊,這里以WInform開(kāi)發(fā)框架中的倉(cāng)庫(kù)管理系統(tǒng)作為主業(yè)務(wù)模塊,它本身也是插件應(yīng)用之一,具體的項(xiàng)目工程結(jié)構(gòu)以及說(shuō)明如下所示。
從上面的表格說(shuō)明中,我們可以看到“WHC.Framework.StarterDx”項(xiàng)目工程,是“插件應(yīng)用框架啟動(dòng)模塊”,它基本上只和權(quán)限管理系統(tǒng)模塊有關(guān)聯(lián)關(guān)系,因?yàn)闄?quán)限系統(tǒng)是框架底層支撐的模塊,包括用戶登錄、菜單管理、權(quán)限控制等都需要從權(quán)限管理系統(tǒng)中獲取數(shù)據(jù),具體的主要業(yè)務(wù)功能如下所示。
2、框架的菜單動(dòng)態(tài)加載本文第一張圖片里面,介紹了菜單的定義信息,其中包括了圖標(biāo)的配置,這些圖片為了方便管理,以及插件需要?jiǎng)討B(tài)添加菜單圖標(biāo),我把它放置在了程序目錄的相對(duì)路徑下面,如下所示,動(dòng)態(tài)創(chuàng)建菜單的時(shí)候,從指定的路徑去獲取圖標(biāo)并加載即可。
動(dòng)態(tài)加載菜單是指在插件化應(yīng)用框架啟動(dòng),用戶登錄后進(jìn)入主界面后,在主界面中動(dòng)態(tài)創(chuàng)建相應(yīng)的菜單(菜單在權(quán)限管理系統(tǒng)中進(jìn)行配置管理),如下代碼所示。
其中是RibbonPageHelper為了方便動(dòng)態(tài)創(chuàng)建菜單而創(chuàng)建的輔助類,部分代碼如下所示。 /// <summary> /// 動(dòng)態(tài)創(chuàng)建RibbonPage和其下面的按鈕項(xiàng)目輔助類 /// </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級(jí),第一級(jí)為大的類別,第二級(jí)為小模塊分組,第三級(jí)為具體的菜單 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) { //如果沒(méi)有菜單的權(quán)限,則跳過(guò) if (!Portal.gc.HasFunction(firstInfo.FunctionId)) continue; //添加頁(yè)面(一級(jí)菜單) 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) { //如果沒(méi)有菜單的權(quán)限,則跳過(guò) if (!Portal.gc.HasFunction(secondInfo.FunctionId)) continue; //添加RibbonPageGroup(二級(jí)菜單) 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) { //如果沒(méi)有菜單的權(quán)限,則跳過(guò) if (!Portal.gc.HasFunction(thirdInfo.FunctionId)) continue; //添加功能按鈕(三級(jí)菜單) 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級(jí)菜單,三個(gè)層級(jí)的菜單示意圖如下所示。 啟動(dòng)頂部的選項(xiàng)卡級(jí)別為第一級(jí),下面的Ribbon分組為第二級(jí),具體的功能菜單(或者按鈕)為第三級(jí),以上就是通過(guò)菜單數(shù)據(jù)動(dòng)態(tài)創(chuàng)建的菜單界面圖。 3、框架的用戶信息和權(quán)限控制基礎(chǔ)框架需要傳統(tǒng)的登錄進(jìn)行驗(yàn)證,登錄成功后,把用戶關(guān)聯(lián)的具有的權(quán)限下載到本地,然后由系統(tǒng)邏輯統(tǒng)一判斷即可。 插件應(yīng)用框架系統(tǒng)的登錄代碼和普通的差別不大,登錄后把相關(guān)信息存儲(chǔ)在框架變量中,如下所示。
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); } } } #endregion bLogin = true; Portal.gc.UserInfo = info; Portal.gc.LoginUserInfo = ConvertToLoginUser(info); this.DialogResult = DialogResult.OK; } } else { MessageDxUtil.ShowTips("用戶賬號(hào)密碼不正確"); this.tbPass.Text = ""; //設(shè)置密碼為空 } } catch (Exception err) { MessageDxUtil.ShowError(err.Message); } } 為了使框架記錄的權(quán)限信息、用戶數(shù)據(jù)、以及系統(tǒng)的一些配置信息能夠傳遞到每個(gè)插件應(yīng)用的窗體中,設(shè)計(jì)了一個(gè)插件應(yīng)用界面需要實(shí)現(xiàn)的接口,放在了BaseUI項(xiàng)目工程中。 namespace WHC.Framework.BaseUI { /// <summary> /// 父窗體實(shí)現(xiàn)的權(quán)限控制接口 /// </summary> public interface IFunction { /// <summary> /// 初始化權(quán)限控制信息 /// </summary> void InitFunction(LoginUserInfo userInfo, Dictionary<string, string> functionDict); /// <summary> /// 是否具有訪問(wèn)指定控制ID的權(quán)限 /// </summary> /// <param name="controlId">功能控制ID</param> /// <returns></returns> bool HasFunction(string controlId); /// <summary> /// 登陸用戶基礎(chǔ)信息 /// </summary> LoginUserInfo LoginUserInfo { get; set; } /// <summary> /// 登錄用戶具有的功能字典集合 /// </summary> Dictionary<string, string> FunctionDict { get; set; } /// <summary> /// 應(yīng)用程序基礎(chǔ)信息 /// </summary> AppInfo AppInfo { get; set; } } } 然后在BaseUI的項(xiàng)目中,界面基類BaseForm實(shí)現(xiàn)這個(gè)接口。 namespace WHC.Framework.BaseUI { public partial class BaseForm : DevExpress.XtraEditors.XtraForm, IFunction { public BaseForm() { InitializeComponent(); } ................... 最后,就是我們?nèi)绾蝹鬟f用戶信息以及權(quán)限信息到窗體本身,傳遞到窗體作為其本身的變量后,就可以很方便使用這些關(guān)鍵的信息了。 在我們動(dòng)態(tài)加載插件應(yīng)用的后,我們會(huì)創(chuàng)建對(duì)應(yīng)的Form對(duì)象,然后轉(zhuǎn)換為IFunction接口,賦予該接口相關(guān)的變量屬性即可實(shí)現(xiàn)用戶信息及權(quán)限信息的傳遞,如下代碼所示。 Form tableForm = (Form)Activator.CreateInstance(formType); //如果窗體集成了IFunction接口(第一次創(chuàng)建需要設(shè)置) IFunction function = tableForm as IFunction; if (function != null) { //初始化權(quán)限控制信息 function.InitFunction(Portal.gc.LoginUserInfo, Portal.gc.FunctionDict); //記錄程序的相關(guān)信息 function.AppInfo = new AppInfo(Portal.gc.AppUnit, Portal.gc.AppName, Portal.gc.AppWholeName, Portal.gc.SystemType); } 4、插件應(yīng)用的動(dòng)態(tài)加載上面我們說(shuō)到,只要是實(shí)現(xiàn)基于Form的,我們都可以動(dòng)態(tài)創(chuàng)建方式調(diào)用顯示插件的界面出來(lái),而如果界面實(shí)現(xiàn)了IFucntion的權(quán)限控制接口,那么我們就能夠傳遞給它響應(yīng)的數(shù)據(jù),實(shí)現(xiàn)更加完善的控制功能。 在第一張關(guān)于權(quán)限系統(tǒng)的菜單管理圖片中,我們看到了有個(gè)Winform的窗體類型的字段,里面就是用來(lái)動(dòng)態(tài)構(gòu)造插件的配置信息,我們主要是用來(lái)構(gòu)造插件的窗體,并傳遞給它相關(guān)數(shù)據(jù)即可,下圖是菜單管理里面的 “Winform窗體類型” 信息的具體內(nèi)容。
但我們完成菜單的動(dòng)態(tài)創(chuàng)建后,菜單按鈕的響應(yīng)事件就是觸發(fā)動(dòng)態(tài)加載插件的事件。 我們添加菜單的時(shí)候,對(duì)它的響應(yīng)事件也做了處理,具體代碼如下所示。 //添加功能按鈕(三級(jí)菜單) 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); 單擊事件的響應(yīng)處理就是動(dòng)態(tài)構(gòu)建插件應(yīng)用的事件,其中就是根據(jù)“Winform窗體類型”的數(shù)據(jù)進(jìn)行解析的。 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); } } 通過(guò)動(dòng)態(tài)創(chuàng)建菜單模塊,動(dòng)態(tài)加載插件應(yīng)用,以及權(quán)限控制等管理,我們就能隔離框架本身和插件應(yīng)用模塊之間的耦合性關(guān)聯(lián),所有后續(xù)開(kāi)發(fā)或者別人開(kāi)發(fā)的業(yè)務(wù)模塊,都可以很方便的通過(guò)權(quán)限管理系統(tǒng)配置數(shù)據(jù)、自動(dòng)更新模塊更新程序應(yīng)用的方式,把一個(gè)高效、易于擴(kuò)展、動(dòng)態(tài)管理的系統(tǒng)應(yīng)用弄得豐富多彩,有聲有色。 基于插件化應(yīng)用框架的Winform開(kāi)發(fā)框架改造,使得今后開(kāi)發(fā)業(yè)務(wù)系統(tǒng),只是基于一定的接口協(xié)議,開(kāi)發(fā)插件應(yīng)用即可,整體性的框架本身可以有專門的人員進(jìn)行維護(hù),提高團(tuán)隊(duì)對(duì)業(yè)務(wù)模塊的橫向切割和快速開(kāi)發(fā)的效率,更好、統(tǒng)一、高效完成企業(yè)化應(yīng)用框架的搭建和使用。 下面的圖形是之前Winform開(kāi)發(fā)框架的相關(guān)功能點(diǎn)集合,加上目前框架的“支持插件化框架應(yīng)用,能快速開(kāi)發(fā)插件、支持動(dòng)態(tài)擴(kuò)展”的特點(diǎn),就顯得更加豐富完善了。
主要研究技術(shù):代碼生成工具、Visio二次開(kāi)發(fā)、送水管理軟件等共享軟件開(kāi)發(fā)專注于Winform開(kāi)發(fā)框架、WCF開(kāi)發(fā)框架的研究及應(yīng)用。 轉(zhuǎn)載請(qǐng)注明出處: 撰寫人:伍華聰 http://www. |
|
|
來(lái)自: 昵稱10504424 > 《工作》