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开发框架的相关功能点集合,加上目前框架的“支持插件化框架应用,能快速开发插件、支持动态扩展”的特点,就显得更加丰富完善了。

本文转自博客园伍华聪的博客,原文链接:Winform开发框架之插件化应用框架实现,如需转载请自行联系原博主。

时间: 2024-10-24 09:16:42

Winform开发框架之插件化应用框架实现的相关文章

WCF开发框架之插件化应用模式升级

自从在<Winform开发框架之插件化应用框架实现>一文中,介绍并总结了Winform开发框架插件化应用框架的实现后,赢得了很多同行和客户的支持,于是把我的WCF开发框架.混合式开发框架都进行了升级,把它们都提升到插件化应用的高度上.本文主要介绍WCF开发框架,如何实现插件化的应用.从我随笔<基于我的Winform开发框架扩展而成的WCF开发框架>介绍可以看到,一般的WCF应用,是在客户端添加服务应用的方式,然后使用自动生成的WCF服务客户端代理来访问相应的服务的,这种方式比较方便

Winform开发框架之客户关系管理系统(CRM)的开发总结系列2-基于框架的开发过程

在上篇随笔<Winform开发框架之客户关系管理系统(CRM)的开发总结系列1-界面功能展示>中介绍了我的整个CRM系统的概貌,本篇继续本系列的文章,介绍如何基于我的<winform开发框架>的基础上进行CRM系统模块的开发工作,希望对大家在系统模块开发有所启示或者帮助. 在我整个开发框架的体系结构中,我都希望开发的业务模块尽可能重用,因此遵循这个要求,所有的模块除了一些基础模块外,尽可能和其他业务模块没有任何耦合关系,同时也可以动态对模块进行加载使用,和我在<Winform

Winform开发框架之框架演化

国庆去了一趟北京,回来一直忙着各种各样的事情,有公司的,有个人的,就没停过来,所以很久没有写博客了,写博客要有一个好的心情及一个好的思路,否则宁愿不写,这是我的准则.虽然这段时间没有写博客分享各种经验,不过一直还在做一些框架相关的事情,很多东西沉淀下来,慢慢有空就继续分享,与大家做一些探讨研究了. Winform开发框架方面的文章我介绍很多了,有宏观介绍,也有部分技术细节的交流,每次我希望能从不同角度,不同方面来介绍我的WInform开发框架,这些其实都是来源于客户的需求,真实的项目场景.本文主

Winform开发框架的重要特性总结

从事Winform开发框架的研究和推广,也做了有几个年头了,从最初的项目雏形到目前各种重要特性的加入完善,是经过了很多项目的总结归纳和升华,有些则是根据客户需要或者应用前景的需要进行的完善,整个Winform开发框架具有很好的通用性和借鉴性,本文从该Winform开发框架进行概括总结,力求把各个重要的特性进行一些详细的说明,使大家了解整个Winform开发框架的面貌和特性. Winform开发框架总体性概括有:支持插件化应用开发,能够动态配置及加载开发的插件:菜单支持动态配置,可根据用户权限进行

携程Android App的插件化和动态加载框架

携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实现细节,回顾携程Android App的架构演化过程,期望我们的经验能帮助到更多的Android工程师. 需求驱动 2014年,随着业务发展需要和携程无线部门的拆分,各业务产品模块归属到各业务BU,原有携程无线App开发团队被分为基础框架.酒店.机票.火车票等多个开发团队,从此携程App的开发和发布

Winform开发框架之混合型框架的实现

我在之前一篇文章<Winform开发框架之框架演化>中,介绍了传统Winform开发框架.传统WCF开发框架.离线式WCF开发框架.混合式WCF开发框架,其中前面两种就是大家比较熟悉的框架了,后面的离线式WCF开发框架,我在<Winform开发之离线式WCF开发框架的实现介绍>一文中也做了阐述,离线式的WCF开发框架,可以看做为传统Winform开发框架+WCF同步模块而成,本文继续探讨这方面的框架设计和实现,重点介绍混合式WCF开发框架的设计思路及具体实现. Winform开发框

Winform开发框架之通用定时服务管理

做项目的时候,或多或少需要和其他外部系统或者接口进行数据交互,有些是单向的获取,有些可能是修改状态后再写回去,不管如何,这个都可以称之为数据同步操作,如人员信息同步.业务数据同步.第三方接口数据同步等等. 数据同步涉及到一个同步时间的问题,一般不敏感的数据,一天或者一周左右同步一次就可以了,有些可能需要间隔更短一点. 同步的逻辑不同,有些可能写数据库就可以了,有些可能需要访问WebService或者其他接口,然后在进行数据获取,保存等操作,回写的时候,也一般是调用WebService这样的接口修

Winform开发框架之通用短信邮件通知模块

在做Winform项目的时候,一直有一个梦想,就是希望把所有的组件模块组合即可组装成一个完整的项目系统(或者至少可以大部分完成).在之前介绍的<Winform开发框架之通用附件管理模块>里面介绍了我的Winform开发框架的版图,里面包含了我对Winform模块化的一系列规划的组件,组件尽可能是适用于大多数的业务环境组合,以达到最大程度的重用和高效开发. Winform开发框架是我集多年开发经验以及积累而成,很多细节之处润物细无声,但却是精粹心得所至,很多地方都希望是精益求精,力求把框架中的模

Winform开发框架的业务对象统一调用方式

在这个纷繁的社会里面,统一性的特点能够带来很多高效的产出.牢固的记忆,这种特征无论对于企 业.个人的开发工作,知识的传承都有着非常重要的作用,Winfrom框架本身就是基于这个理念而生,从 统一的数据库设计规则开始,统一的项目格局,统一的业务类.数据访问类.实体类继承关系,再到统 一的公用类库,统一的权限管理模块,统一的字典管理模块,统一的附件管理...,理解这些理念和规则 后,再来个终极的统一,框架代码快速生成--Database2Sharp代码生成工具.所有的框架(包括传统 Winform开