Winform開發框架之插件化應用框架實現

支持插件化應用的開發框架能給程序帶來無窮的生命力,也是目前不少系統、程序追求的重要方向之一,插件化的模塊,在遵循必定的接口標準的基礎上,能夠實現快速集成,也就是所謂的熱插拔操做,能夠無限對已經開發好系統進行擴展,並且不會影響已有的功能,不在須要的模塊,經過修改配置移除便可。個人Winform開發框架一直以來,來源於多年的項目積累以及客戶的反饋,已經具有了衆多很好的特性以及相關的模塊組合,爲了更好擁抱變化,提升基於Winform開發框架基礎上開發新系統的效率,以及爲框架融入更多好的特性,故此把個人Winform開發框架在原來的基礎上進行擴展,實現基於插件化應用框架特性。html

爲了引入插件化的應用框架特色,我在上一篇隨筆《Winform開發框架之權限管理系統的改進》已經對個人通用權限管理系統進行了改進,其中增長了菜單管理模塊就是爲了作插件化作準備的,咱們經過權限管理系統配置好菜單的相關信息,而後在應用框架中動態加載菜單功能便可實現。這個菜單模塊,是用來配置基於Web開發框架或者Winform開發框架、WCF開發框架的菜單,經過預先的配置,框架程序的動態加載解析,就能實現插件模塊的熱插拔功能了。實際插件化框架的菜單配置界面效果以下所示。框架

最終在Winform開發框架的程序中,實現基於插件化的應用,以下所示。ide

先來看看我改造Winform開發框架,最終造成的框架界面效果,而後在逐一進行介紹,整個開發框架的實現過程。post

一、框架的項目工程規劃

爲了減小框架總體的複雜性以及提升重用,對插件化的應用框架的項目工程進行了劃分,包括「框架基礎界面模塊」、「插件應用框架啓動模塊」、倉庫管理系統模塊業務邏輯、倉庫管理系統模塊窗體界面等幾個部分。前面兩個部分是插件化框架的核心,能夠認爲是不須要變化的模塊,提供全部插件應用動態建立以及使用的框架支撐;後面兩個是具體的主業務模塊,這裏以WInform開發框架中的倉庫管理系統做爲主業務模塊,它自己也是插件應用之一,具體的項目工程結構以及說明以下所示。this

項目名稱 項目說明
WHC.Framework.BaseUIDx  框架基礎界面模塊,定義窗體界面基類、通用Excel導入模塊、通用高級查詢模塊等
WHC.Framework.StarterDx  插件應用框架啓動模塊,集成權限登陸、動態菜單建立、插件應用動態加載、基礎框架功能等
WHC.WareHouseMis  倉庫管理系統模塊的業務邏輯
WHC.Framework.WareHouseDx   倉庫管理系統模塊的窗體界面

從上面的表格說明中,咱們能夠看到「WHC.Framework.StarterDx」項目工程,是「插件應用框架啓動模塊」,它基本上只和權限管理系統模塊有關聯關係,由於權限系統是框架底層支撐的模塊,包括用戶登陸、菜單管理、權限控制等都須要從權限管理系統中獲取數據,具體的主要業務功能以下所示。url

 

 

二、框架的菜單動態加載

 本文第一張圖片裏面,介紹了菜單的定義信息,其中包括了圖標的配置,這些圖片爲了方便管理,以及插件須要動態添加菜單圖標,我把它放置在了程序目錄的相對路徑下面,以下所示,動態建立菜單的時候,從指定的路徑去獲取圖標並加載便可。spa

動態加載菜單是指在插件化應用框架啓動,用戶登陸後進入主界面後,在主界面中動態建立相應的菜單(菜單在權限管理系統中進行配置管理),以下代碼所示。插件

其中是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級菜單,三個層級的菜單示意圖以下所示。3d

啓動頂部的選項卡級別爲第一級,下面的Ribbon分組爲第二級,具體的功能菜單(或者按鈕)爲第三級,以上就是經過菜單數據動態建立的菜單界面圖。

三、框架的用戶信息和權限控制

基礎框架須要傳統的登陸進行驗證,登陸成功後,把用戶關聯的具備的權限下載到本地,而後由系統邏輯統一判斷便可。

插件應用框架系統的登陸代碼和普通的差異不大,登陸後把相關信息存儲在框架變量中,以下所示。

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

 四、插件應用的動態加載

上面咱們說到,只要是實現基於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開發框架改造,使得從此開發業務系統,只是基於必定的接口協議,開發插件應用便可,總體性的框架自己能夠有專門的人員進行維護,提升團隊對業務模塊的橫向切割和快速開發的效率,更好、統1、高效完成企業化應用框架的搭建和使用。

下面的圖形是以前Winform開發框架的相關功能點集合,加上目前框架的「支持插件化框架應用,能快速開發插件、支持動態擴展」的特色,就顯得更加豐富完善了。

相關文章
相關標籤/搜索