十八、【开源】EnterpriseFrameWork框架核心类库之Winform控制器
回《【开源】EnterpriseFrameWork框架系列文章索引》
EFW框架源代码下载:http://pan.baidu.com/s/1qWJjo3U
EFW框架中的WinController控制器可以说是整个Winform版中最具有价值的地方,能够熟练使用它的话,可以让你写得代码结构清晰不知多少倍,真正的做到了CS开发的界面层与逻辑层的完全隔离;更重要的是改变了你写界面代码的思维,让你一次性写出功能完善的代码,真的,传统的那种事件驱动的方式编码会让你的代码变得越来越臃肿,就算你懂得不断重构你的代码,也完全避免不了出现臃肿的情况;所以必须使用WinController控制器模式从源头彻底解决这种问题;
WinController控制器与界面不一定是一一对应的,一个控制器可以对应多个界面,当几个界面的操作十分密切时就可以用一个控制器来控制它们的行为;一个界面对应多个控制器这种方式最好不要这么做,这样会让你的程序变得复杂,也破坏了业务架构与框架之间的关系,参考第十一章《EnterpriseFrameWork框架的分层与系统业务的结合》。
本章主要内容通过解读框架源代码来学习WinController是怎么实现的,以及学习控制器这种设计模式;
本文要点:
1.如何使用Winform控制器
2.Winform控制器的设计思路
3.Winform控制器BaseController基类的实现代码
4.Winform控制器的自定义标签MenuAttribute和ViewAttribute
5.通过Winform控制器打开界面
Winform控制器源代码目录结构
EFW框架控制器设计图
1.如何使用Winform控制器
如上图,Books.Winform项目里的是界面文件,Books项目的是界面接口文件和控制器文件,frmBookManager对象继承IfrmBook接口,bookwinController对象依IfrmBook接口;
见上图,Books实例中保存数据功能代码调用流程,界面代码frmBookManager继承了IfrmBook接口的两个方法并实现,点击保存按钮,btnsave_Click事件内并不实现保存数据的代码而只是向控制器发送一个消息,指定需要调用的方法名称bookSave,程序开始执行控制器中的bookSave方法,利用接口属性frmBook.currBook从界面获取数据,并利用框架中的ORM保存到数据库;接着调用GetBooks方法将数据显示在界面上;GetBooks方法调用后台的IBookDao对象获取Book数据,再通过frmBook.loadbooks(dt)接口方法将数据绑定到界面控件gridBook上;
2.Winform控制器的设计思路
Winform控制器的设计思路有点类似Web系统中的MVC模式,控制器处理这后台数据对象与前台界面直接的交互,利用接口从界面获取数据,再传递给后台对象处理,处理完后再利用接口将数据传递给界面控件显示;整个交互的逻辑代码是在控制器中实现的,数据怎么从界面获取与数据怎么绑定到界面控件都是在前端界面接口中实现的,而数据怎么存储到数据库与怎么从数据库获取数据都是在后端Dao或ObjectModel中实现的,当然也可以在控制器中利用oleDb直接操作数据库,但好的程序结构还是放在Dao和ObjectModel中合适些;
Winform控制器必须继承框架中的BaseController对象,还有控制器类名上要指定菜单标签MenuAttribute和界面标签ViewAttribute,BaseController对象封装了控制器的一些处理功能,MenuAttribute和ViewAttribute一个对应系统的菜单名,一个决定控制器能操作的界面对象;
3.Winform控制器BaseController基类的实现代码
因为所有Winform控制器都必须继承BaseController基类,所以我们有必要先看一下BaseController基类的代码是怎么样的;
BaseController文件
1 /// <summary> 2 /// Winform控制器基类 3 /// </summary> 4 public abstract class BaseController : AbstractBusines 5 { 6 public AbstractDatabase oleDb 7 { 8 get 9 { 10 return _oleDb; 11 } 12 } 13 14 public SysLoginRight GetSysLoginRight 15 { 16 get 17 { 18 if (EFWCoreLib.CoreFrame.Init.AppGlobal.cache.GetData("RoleUser") != null) 19 { 20 return (SysLoginRight)EFWCoreLib.CoreFrame.Init.AppGlobal.cache.GetData("RoleUser"); 21 } 22 else 23 { 24 return new SysLoginRight(); 25 } 26 } 27 } 28 29 internal IBaseView _defaultView; 30 31 public IBaseView DefaultView 32 { 33 get { return _defaultView; } 34 } 35 36 private Dictionary<string, IBaseView> _iBaseView; 37 public Dictionary<string, IBaseView> iBaseView 38 { 39 get { return _iBaseView; } 40 set 41 { 42 _iBaseView = value; 43 foreach (KeyValuePair<string, IBaseView> val in _iBaseView) 44 { 45 //val.Value.ControllerEvent += new ControllerEventHandler(UI_ControllerEvent); 46 val.Value.InvokeController = new ControllerEventHandler(UI_ControllerEvent); 47 } 48 } 49 } 50 51 public CloseTab closeTab; 52 53 /// <summary> 54 /// 创建BaseController的实例 55 /// </summary> 56 public BaseController() 57 { 58 59 } 60 /// <summary> 61 /// 界面控制事件 62 /// </summary> 63 /// <param name="eventname">事件名称</param> 64 /// <param name="objs">参数数组</param> 65 /// <returns></returns> 66 public virtual object UI_ControllerEvent(string eventname, params object[] objs) 67 { 68 switch (eventname) 69 { 70 case "Close": 71 if (closeTab != null) 72 closeTab(); 73 break; 74 case "this": 75 return this; 76 } 77 78 MethodInfo meth = this.GetType().GetMethod(eventname); 79 if (meth != null) 80 { 81 MethodAttribute[] WinM = (MethodAttribute[])meth.GetCustomAttributes(typeof(MethodAttribute), true); 82 if (WinM.Length > 0) 83 { 84 if (WinM[0].OpenDBKeys != null && WinM[0].OpenDBKeys.ToString().Trim() != "") 85 { 86 List<string> dbkeys = WinM[0].OpenDBKeys.Split(new char[] { ',' }).ToList(); 87 this.BindMoreDb(oleDb, "default"); 88 foreach (string dbkey in dbkeys) 89 { 90 EFWCoreLib.CoreFrame.DbProvider.AbstractDatabase _Rdb = EFWCoreLib.CoreFrame.DbProvider.FactoryDatabase.GetDatabase(dbkey); 91 _Rdb.WorkId = GetSysLoginRight.WorkId; 92 //创建数据库连接 93 this.BindMoreDb(_Rdb, dbkey); 94 } 95 } 96 } 97 return meth.Invoke(this, objs); 98 } 99 return null; 100 } 101 102 /// <summary> 103 /// 初始化全局web服务参数对象 104 /// </summary> 105 public virtual void Init() { } 106 107 public virtual IBaseView GetView(string frmName) 108 { 109 return iBaseView[frmName]; 110 } 111 }
BaseController基类封装了控制器的四方面的功能:
1)数据库操作对象oleDb,这样我们可以直接在控制器方法内使用它执行SQL语句操作数据库
2)系统登录后的用户信息GetSysLoginRight,这样我们可以再控制器中很方便的使用当前登录的用户信息;
3)界面对象字典iBaseView,这属性与控制器上ViewAttribute标签配置相对应,可以从中取出配置的界面接口对象来操作界面;
4)处理界面请求控制器方法的功能UI_ControllerEvent,界面使用InvokeController向控制器发送消息,而怎么效应界面的消息在此方法中实现的,利用反射机制执行对应的控制器方法;
4.Winform控制器的自定义标签MenuAttribute和ViewAttribute
这里的设计是这样的,一个控制器可以配置多个MenuAttribute,那么系统可以挂多个菜单,而这几个菜单都是公用这一个控制器,同样一个控制器可以配置多个ViewAttribute,控制器就可以同时操作这些配置的界面;
MenuAttribute文件
1 [AttributeUsageAttribute(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] 2 public class MenuAttribute:Attribute 3 { 4 5 string _defaultName; 6 7 /// <summary> 8 /// 菜单名称 9 /// </summary> 10 public string DefaultName 11 { 12 get { return _defaultName; } 13 set { _defaultName = value; } 14 } 15 16 private string _defaultViewName; 17 /// <summary> 18 /// 菜单对应打开界面 19 /// </summary> 20 public string DefaultViewName 21 { 22 get { return _defaultViewName; } 23 set { _defaultViewName = value; } 24 } 25 26 string _memo; 27 public string Memo 28 { 29 get { return _memo; } 30 set { _memo = value; } 31 } 32 33 public MenuAttribute() 34 { 35 } 36 37 public MenuAttribute(string defaultName,string memo) 38 { 39 this._defaultName = defaultName; 40 this._memo = memo; 41 } 42 }
ViewAttribute文件
1 [AttributeUsageAttribute(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 2 public class ViewAttribute:Attribute 3 { 4 private string _name; 5 /// <summary> 6 /// 界面名称 7 /// </summary> 8 public string Name 9 { 10 get { return _name; } 11 set { _name = value; } 12 } 13 14 private bool _defaultShow; 15 public bool DefaultView 16 { 17 get { return _defaultShow; } 18 set { _defaultShow = value; } 19 } 20 21 private Type _viewType; 22 /// <summary> 23 /// 界面对象类型 24 /// </summary> 25 public Type ViewType 26 { 27 get { return _viewType; } 28 set { _viewType = value; } 29 } 30 31 private string _dllName; 32 /// <summary> 33 /// 界面存放的DLL 34 /// </summary> 35 public string DllName 36 { 37 get { return _dllName; } 38 set { _dllName = value; } 39 } 40 41 private string _viewTypeName; 42 /// <summary> 43 /// 界面类型名称 44 /// </summary> 45 public string ViewTypeName 46 { 47 get { return _viewTypeName; } 48 set { _viewTypeName = value; } 49 } 50 51 string _memo; 52 public string Memo 53 { 54 get { return _memo; } 55 set { _memo = value; } 56 } 57 }
5.通过Winform控制器打开界面
Winform系统打开一个界面是这样操作的,用户点击系统主界面上的菜单,框架就会调用WinformViewCreator对象来实例化控制器对象和实例化控制器配置的所有界面窗体对象,所以我们有必要看一下WinformViewCreator对象是怎么实现的;
WinformViewCreator文件
1 public override Object InstanceController(CloseTab close) 2 { 3 try 4 { 5 6 //加载类库 7 Assembly assembly = null; 8 assembly = Assembly.LoadFrom(AppGlobal.AppRootPath + "\\" + dllfile); 9 10 //获得控制器类(型) 11 Type type = assembly.GetType(controllername, false, true); 12 13 MenuAttribute[] menuAttribute = (MenuAttribute[])type.GetCustomAttributes(typeof(MenuAttribute), true); 14 ViewAttribute[] viewAttribute = (ViewAttribute[])type.GetCustomAttributes(typeof(ViewAttribute), true); 15 //ServiceAttribute[] serviceAttribute = (ServiceAttribute[])type.GetCustomAttributes(typeof(ServiceAttribute), true); 16 17 if (menuAttribute.Length > 0) 18 { 19 Dictionary<string, IBaseView> viewDic = new Dictionary<string, IBaseView>(); 20 //Dictionary<string, SoapHttpClientProtocol> serviceDic = new Dictionary<string, SoapHttpClientProtocol>(); 21 BaseController controller = (BaseController)System.Activator.CreateInstance(type); 22 23 EFWCoreLib.CoreFrame.DbProvider.AbstractDatabase Rdb = EFWCoreLib.CoreFrame.DbProvider.FactoryDatabase.GetDatabase(); 24 Rdb.WorkId = controller.GetSysLoginRight.WorkId; 25 controller.BindDb(Rdb, AppGlobal.container); 26 27 controller.closeTab = close;//关闭窗口 28 29 for (int index = 0; index < viewAttribute.Length; index++) 30 { 31 if (viewAttribute[index].ViewType == null) 32 { 33 if (string.IsNullOrEmpty(viewAttribute[index].DllName)) 34 { 35 continue; 36 } 37 Assembly _assembly = Assembly.LoadFrom(AppGlobal.AppRootPath + "\\" + viewAttribute[index].DllName); 38 viewAttribute[index].ViewType = _assembly.GetType(viewAttribute[index].ViewTypeName, false, true); 39 } 40 IBaseView view = (IBaseView)System.Activator.CreateInstance(viewAttribute[index].ViewType); 41 if (viewAttribute[index].DefaultView) controller._defaultView = view; 42 if (index == 0 && viewAttribute.ToList().FindIndex(x => x.DefaultView == true) == -1) controller._defaultView = view; 43 viewDic.Add(viewAttribute[index].ViewType.Name, view); 44 } 45 //for (int index = 0; index < serviceAttribute.Length; index++) 46 //{ 47 // SoapHttpClientProtocol service = (SoapHttpClientProtocol)System.Activator.CreateInstance(serviceAttribute[index].ServiceType); 48 // serviceDic.Add(serviceAttribute[index].ServiceType.Name, service); 49 //} 50 51 controller.iBaseView = viewDic; 52 //controller.service = serviceDic; 53 54 controller.Init();//初始化 55 return controller; 56 } 57 58 return null; 59 } 60 catch (Exception err) 61 { 62 throw err; 63 } 64 }
上面代码中有两点需要说明一下,首先根据菜单配置的控制器名称通过反射实例化控制器对象;
接着根据控制器的配置的ViewAttribute标签,实例化界面对接并添加到iBaseView属性中;