通用性站点管理后台(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.MvcDispatcherBee.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的方式其实很简单。 关键是在实现《【讨论】一个接口的世界》的时候, 要注意性能。

posted @ 2012-12-10 16:26    阅读(3120)  评论(0编辑  收藏  举报