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);
                                }
                            }
                        }

                        #endregion

                        bLogin = 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開發框架的相關功能點集合,加上目前框架的“支持插件化框架應用,能快速開發插件、支持動態擴展”的特點,就顯得更加豐富完善了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM