通用性站点管理后台(Bee OPOA Platform) (3)- MVC特性
接上篇(通用性站点管理后台(Bee OPOA Platform) (2)- 快速开发特性)
当时在系统构建的一开始就想引入MVC特性, 本人比较偏向于这种方式, 对Asp.net 基于事件这种方式不是特别兴趣。 对纯粹的http的调用方式很喜欢(可以用Fiddler拦截, 以便查找原因), 以最基本的Get/Post方式及请求的参数列表, 则可以很清晰的知道该请求与服务器端对应的关系。
配置
该特性开发目前基于以下配置文件展开的(IIS 7跟这个有点不一样, 具体可以参看Codeplex项目中的web.config文件):
<add verb="*" path="*/*.bee,*.*.bee" type="Bee.Web.AuthMvcDispatcher, Bee.Security" validate="true" />
Bee.Web.AuthMvcDispatcher类型集成了RBAC权限中的权限判断, 会在前端发起请求的时候统一判断该请求是否有权限, 若没有, 则返回一个Json结构的结果。该类型继承与Bee.Web.MvcDispatcher。 Bee.Web.MvcDispatcher是继承接口有IHttpHandler,IRequiresSessionState,该Handler将处理所有后缀为bee的请求。
Action执行
目前只支持相当于Asp.net MVC框架中的最简单的路由规则, 即/{ControllerName}/{ActionName}.bee的方式。 获取了ControllerName, 就要获得对应的Controller类型。
如何获取ControllerName对应的Controller类型呢? 这个往往是先收集程序集中所有可能的Controller类型, 即扫描所有程序集放到一个对应的Dictionary集合中, 如:
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) { string s = a.FullName.Split(',')[0]; if (!s.StartsWith("System.") && CouldBeControllerAssemebly(s)) { foreach (var type in assembly.GetTypes()) { if (!type.IsInterface && !type.IsAbstract && type.IsSubclassOf(CbType) && !type.IsGenericType && type.Name.EndsWith("Controller")) { // 符合就放入对应的ControllerName与类型的集合 } } } }
有心的人很容易看出来这里有个问题, 什么问题呢? 就是不同NameSpace相同ControllerName的问题。 这个的问题解决与否, 其实取决于系统的应用, 若不是设计像微软这样做基础性工作的设计的话, 完全可以不需要考虑该情形。 您认同吗? 本平台对该类情形未作考虑。对应关系找出来后, 生成一个Controller实例就无问题了。 现在问题转化到:给你一个类, 根据掺入的方法名及参数, 请动态调用尽可能合适的方式。
回头看《【讨论】一个接口的世界》, 就相当于要实现方法 object Invoke(object entity, string methodName, BeeDataAdapter dataAdapter);
那实现该方法的关键在于哪里呢?本人的解决方案的是通过Emit动态生成对应的代理类。 下面将结合实例(该实例可通过http://beeopoa.codeplex.com获得, 通过svn方式可获得源码)。
我建立如下的Controller:
public class MVCTestController : ControllerBase { public int Add(int i, int j) { return i + j; } public int Add(int i) { return i; } public int Add(int i, BeeDataAdapter dataAdapter) { return 100; } }
对应生成的ControllerProxy的代码(该代码由于是动态产生, 可通过Reflector查看\Bee.OPOADemo\Cache\Bee_Core_EntityProxy\BeeCoreEntityProxy.dll获的。 该dll为程序运行时产生, 在整个应用程序关闭时, 会生成该dll, 以便调试及查看用。)
public class MVCTestControllerProxy : EntityProxy<MVCTestController> { // Methods public override object GetPropertyValue(object obj1, string text1) { MVCTestController controller = (MVCTestController) obj1; if (text1 == "ControllerName") { return controller.ControllerName; } return null; } public override object Invoke(object obj1, string text1, BeeDataAdapter adapter1) { MVCTestController controller = (MVCTestController) obj1;
// 该方法为具体的匹配方法, 该方法已被混淆
A2MkIq94Rgjx rgjx = ReflectionUtil.ABGkd(typeof(MVCTestController), text1, adapter1); string str = rgjx.AAkp5A9X; BeeDataAdapter aBGkd = rgjx.ABGkd; switch (str) { case "Int32 Add(Int32, Bee.BeeDataAdapter)": return controller.Add((int) ConvertUtil.Convert(aBGkd["i"], typeof(int)), (BeeDataAdapter) ConvertUtil.Convert(aBGkd["dataAdapter"], typeof(BeeDataAdapter))); case "Int32 Add(Int32, Int32)": return controller.Add((int) ConvertUtil.Convert(aBGkd["i"], typeof(int)), (int) ConvertUtil.Convert(aBGkd["j"], typeof(int))); case "Int32 Add(Int32)": return controller.Add((int) ConvertUtil.Convert(aBGkd["i"], typeof(int))); } return null; } public override void SetPropertyValue(object obj1, string text1, object obj2) { MVCTestController controller = (MVCTestController) obj1; text1 = text1.ToLower(); if ((obj2 != null) && (obj2 != DBNull.Value)) { } } }
该类的方法GetPropertyValue及SetPropertyValue是针对设计ORM时或者在序列化反序列化的场合下有用, 该篇将讲解Invoke方法。
由于方法名可能相同, 但参数不同的情形。 及c#中关键词out及ref的引入, 对该问题的处理有点繁杂, 我们只关注最需要关注的那部分, 首先在设计之初就不考虑对out及ref的支持。
那么问题基本就回归到参数匹配的问题, 思路如下: 1. 参数匹配以名称匹配为准, 简单类型为主;2.以参数多的开始匹配;3. 复杂类型这参数列表中最多只能有一个。
若能匹配则调用对应的方法, 执行。
Action执行返回值的处理
平台提供了一个ActionResult的基类, 并且提供了扩展类ContentResult(直接发送内容, 如直接的文本), JsonResult(实例的Json格式), PageResult(视图), RedirectResult(页面跳转)
StreamResult(流类型,文本, 或者图片)。
并且在Controller的基类ControllerBase提供了内置方法。 如Json, View。 基本上都还比较好理解, 转化一下加入Response的流中输出。 如何执行对应的PageResult呢? 如何使用不同的视图引擎来渲染呢? 说白了, 其中的过程就相当于一段文本(View, aspx文件, cshtml文件以及其他), 然后有很多值(Model, 及ViewData 或者诸如其他), 然后得到最终输出的结果。 呵呵, 很像基于模板的代码生成器, 。 最经典的莫过于Asp.Net WebForm引擎, 本平台目前也只实现基于此的视图引擎, Razor方式, 及NVelocity, 暂未实现。 因为该方式最简单及Framework中内置的:
object o = System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(object));
该方法直接创建基于路径的page实例, 其中的缓存及路径查找你都不用考虑。 若要实现自己的引擎则需要考虑这些。
总结
c#中实现MVC的方式其实很简单。 关键是在实现《【讨论】一个接口的世界》的时候, 要注意性能。