Maverick.Net介绍

Maverick.Net是Java社区开源MVC Web框架Maverick的.Net版本,相关资料可以查看项目主页。
不管Maverick.Net的是非好坏,了解一下它的思想还是不错的。下面的内容是对Maverick.Net整体做一个简单的介绍,以求能够从全局的角度了解Maverick.Net一些相关概念,大致如何工作。
另外,本人没有使用Maverick.Net开发过项目,只是看了它的架构、源码之后有一些想法,跟大家分享一下,也希望能为想了解Maverick.Net的人提供些参考。
1. 主框架
Maverick.Net处理HttpRequest典型的流程如下图:
5409b562020009q9
图一:处理请求 Reference:MaverickLite Specification
Maverick.Net将每一个HttpRequest映射成Command。很容易看到,Command的提交者为用户界面,也就是View;处理者应当是业务逻辑的控制器,也就是Controller。这样,Maverick.Net Framework在处理每一个Command时,主要是解决下面的问题:如何从用户界面(View)提交的参数生成Model?用哪个Controller处理特定的Command以及如何将Model传递给这个Controller?Controller处理结束之后如何将处理结果呈现给用户(对Maverick.Net而言就是根据Controller处理结果,选择返回哪个View给用户界面)以及如何将Model传递给返回的结果View?
Maverick.Net通过一个配置文件进行配置,典型的Command配置节点如下:
5409b562020009qb
这是Maverick.Net项目例子Friendbook中一个提交用户注册的Command配置节点。Dispatcher接收到提交注册的HttpRequest之后,得到signupSubmit的Command Name,从而可以得到这个Command对象。Command将创建指定的Controller对象Friendbook.Ctl.SignupSubmit.Friendbook,并调用Controller进行处理。Controller处理结果应当是注册成功,需要转向注册成功之后的View;或者是发生错误,需要转向重新填写注册资料的View。Maverick.Net Framework规定Controller的返回结果必须是一个View的名字,这样,Command调用Controller处理之后,就知道应该呈现哪个View对象。上图中,如果注册成功(success),将调用name="success"的View;如果注册出错,例如两次输入的密码不一致等,将调用name="error"的View。name="success"的View类型为redirect,将使用当前的MaverickContext对象(聚合了HttpContext、Model等对象)重定向到另外一个名称为edit.m的Command对象(View的类型等后面再介绍);name="error"的View类型为document(默认类型,不需要写type="document"),将直接使用Server.Execute()方法得到View的HTML输出,并使用Transform对象,将用户注册结果View的HTML输出转换成整体页面的HTML代码,返回给客户端(Transform的概念,后面再介绍)。
2. 主要的类、对象
Maverick.Net主要的类或对象如下:Dispatcher、Command、Controller、View、Transform、MaverickContext。
2.1 Dispatcher
Dispatcher是一个HttpHandler类,用于拦截和处理HttpRequest,HttpRequest-Command的映射就是通过Dispatcher完成。
典型的情况下,Maverick.Net使用.m后缀作为ISAPI扩展,映射到Command。这种情况下,所有浏览器端提交的HttpRequest,请求的都是以.m结束的虚拟文件名,被提交给Dispatcher。这个文件在服务器上并不存在,Dispatcher接收到类似login.m的请求之后,将.m后缀去掉,取login作为Command Name,获取Command对象,处理请求。
Maverick.Net老的版本基本上就是照搬Java版本的Maverick,在View的层面上只支持象JSP中Servlet的写法,也就是纯粹的ASP方式,或者是以ASP方式编写的的aspx页面。ASP.Net环境下ViewState、Postback事件机制、用户控件等这些特性都不能使用,这对于ASP.Net环境是一种很大的损失。1.2版本开始,Maverick.Net的Controller比较完全的支持aspx的服务器端控件、用户控件等,这样就可以使用Maverick.Net框架,又基于ASP.Net进行设计和开发。
在使用aspx类型的Controller时,Dispatcher的拦截作用被去掉了,而是使用ASP.Net默认的HttpHandler,页面类继承Maverick.Ctl.Aspx.ControllablePage,ControllablePage在页面的Render方法中切入Maverick.Net框架的控制。这种情况下,Command Name也不再是.m后缀方式,而直接变成对应的aspx页面名称,例如<command name="Default.aspx">。
另外,Dispatcher处理请求时,负责构造MaverickContext对象,使Command、Controller、View、Transform等在各自的处理期间,都能够得到相应的上下文信息。
2.2 Command
Command的作用类似一个指挥官,它根据Maverick.config文件的配置创建Controller对象,调用Controller进行处理;根据Controller的返回结果,选择相应的View对象并执行。
Command可以没有Controller,这种情况下,对Command的请求,实际上就是将特定的View呈现给客户端。在Maverick.Net中,这是CommandSingleView。
另外,也可以继承ICommand接口实现一些特殊功能或者说自定义的Command,例如Maverick.Net框架本身实现了两个特殊的Command:ReloadCommand和CurrentConfigCommand。CurrentConfigCommand用于查看当前配置文件的内容,这个Command读取Maverick.config配置文件的内容,直接输出配置文件中的xml。另外,Maverick.Net在第一次处理请求的时候,会读取Maverick.config中的内容,根据配置创建Command、View、Transform对象并缓存起来,后续的请求只是从缓存中取相应的对象。如果在第一次访问之后这些配置有更新,这些更新不会立即反映到缓存的对象中。ReloadCommand的作用就是根据当前的Maverick.config配置文件内容,重新创建缓存的对象。
2.3 Controller
Controller的职责是负责业务逻辑的处理。
在典型的应用中,Controller首先通过MaverickContext中的HttpContext对象,获取从用户界面也就是View提交过来的参数,创建Model对象,使用Model完成对请求的业务逻辑处理。Controller不同的处理结果,将用来确定选择哪个View。在Controller的处理过程中可能会对Model进行更新,或者是有一些参数需要传递给View,这些将通过MaverickContext这个上下文信息对象完成。
在aspx类型的Controller中,因为Dispatcher的拦截作用被丢弃,因此MaverickContext对象的创建,也是在Controller中完成的。
2.4 View
MVC的概念中,View负责将某个特定的Model,或者是整个Business Domain Model中某些角度的切片呈现给客户端。Maverick.Net中,将View分成多种类型。
DocumentView:这是默认的View类型。这类型的View将配置一个文件,可能是asp或者aspx或者html等,调用Server.Execute()方法得到HTML的输出,发送给客户端。
ForwardView:这类型的View使用当前的MaverickContext重定向到另外一个Command去执行。如果基于structs的方式做系统分析设计,一个业务操作可能会被分解成很多个Command/Action,将这些Command/Action组合起来就形成一个新的Command/Action,或者就对应到某一个业务操作。forward类型的View是用于对这种形式的支持。
RedirectView:这类型的View,将直接使用Response.Redirect()方法重定向过去。因此也可以将forward类型的View看作是redirect类型的一个特例。实际上在Maverick.Net项目的示例中就有不少这样的用法。这是因为Dispatcher拦截了HttpRequest,类似Response.Redirect("edit.m")的语句执行后,仍然会被Dispatcher拦截,开始一个新的Command的执行。
TrivialView:这类型的View,Maverick.Net将MaverickContext的Model对象直接进行输出(取出内容,直接输出)。这要求MaverickContext的Model对象是下面几种类型:String、System.Text.StringBuilder、System.IO.TextReader、Sytem.Xml.XmlNode。
XmlSerializingView:这类型的View,将MaverickContext的Model序列化成xml格式后输出。这种View主要用于基于xml的网站方案,或者是提供系统接口之用途。
2.5 Transform
在Maverick.Net看来,View执行的结果,还不是呈现给客户端的最终输出,之间必须经由Transform进行相应的转换。
Maverick.Net实现的Transform有:DocumentTransform、XsltTransform。从XsltTransform类型更容易理解Maverick.Net的Transform概念。对应于XsltTransform,View是一个xml或者是XmlSerializing类型的View,为了将这个xml转换成HTML输出,需要使用xsl/xslt文件进行转换,这就是XsltTransform的工作。
对于DocumentTransform的理解,Transform这个单词意义上有点牵强。首先,DocumentTransform是针对于asp、aspx、html这种文档类型的View。在我们的映象里,这种类型的View,Maverick.Net使用的是Server.Execute()方法,这样已经直接得到HTML代码,何必再经过一个DocumentTransform处理?
实际上,对于一个系统,工作页面会被分成不同的区域,例如页面的Banner区、菜单/功能列表区、通用工具栏等等。这些不同的区域,在后面是由不同的Controller负责处理,对应的也是不同的Model。在Command的处理中,特定的Command只会使用自己的Controller对象,负责对相应的Model进行处理,因此也只应当对页面中它所辖区域的显示负责。这样,View执行的结果就只是Command所辖区域的更新,而并不是整个页面。至于如何将这个区域的更新显示在整个页面中,这就是DocumentTransform的工作。
目前Maverick.Net对于DocumentTransform用如下方式进行处理。假如是一个document类型的View,对应一个aspx文件。View在执行时使用Server.Execute()方法得到输出的HTML代码。但这个还不是最终发向客户端的完整输出,而只是其中的一部分,Maverick.Net将这个HTML片断保存在HttpContext.Items[""]中。DocumentTransform对象对应的也是一个aspx的页面,这个页面应当完成系统页面的整体布局,并从HttpContext.Items[""]中取出View执行后的HTML,输出在适当的位置。这样,使用Server.Execute()方法调用DocumentTransform的aspx页面后,就是完整的发向客户端的HTML了。
其实Transform是考虑了页面的整体布局,一个View只会对某个区域负责的状况,但是它并不支持复杂的布局,只能说是轻量级吧。复杂的布局通过iFrame等实现,或者做些修改后结合Ajax方式进行局部更新。
2.6 MaverickContext
这是一个上下文对象,主要负责在Command、Controller、View之间传递信息。它包含HttpContext、Model和一个IDictionary类型的Params属性。
HttpContext对象在创建MaverickContext时由创建者(Dispatcher或者ControllablePage)提供;Model由Controller执行处理时创建;Params用于在需要的传递其它的参数。
从MVC的角度来看处理流程,如下图:
5409b562020009tq
3. 简评
在其它一些介绍Maverick.Net的文章里,有看到不少对Maverick.Net,对ASP.Net下如何运用MVC概念的讨论,看法各不一样。其实对.Net下的开发、架构设计的运用,本来都是仁者见仁,智者见智的事情,只是看大家怎么运用了。下面的这些感想,只是针对企业级应用的架构模式方面,对基于快速开发的小型项目当然另当别论。
3.1 M-V-C分离
ASP.Net本身的确就是一种MVC的框架,可以说是一个很强大又很好用的MVC框架,但是有一些障碍在阻挠着我们。ASP.Net本身的许多特性,使得M-V-C之间的概念变得模糊甚至混乱。
看一下这个场景:添加一个Web Form;用Drag-Drop方式在Form中添加GridView、Connection等数据库组建;设置相应的属性,例如数据库连接,GridView的数据源绑定等。可能就简简单单1、2行代码甚至1行都不需要,运行这个Web Form就将数据显示出来了。一切是这么方便和简单,给BS开发带来一种革命性的思想。可是基于企业级应用架构层面来看,从这样的场景中你能得到什么,想到什么?
当然,对富于经验的人,这根本就不是问题所在。随便写一段代码,就能很好的体现MVC的思想,无所谓使用的哪种语言,什么工具。假如现在需要几百个开发者协同完成一个项目,情况会怎样?ASP.Net本身在架构方面没有太多约束力,尽管它是一个很好的MVC框架。而这种情况下,又非常的需要在架构上对协作的开发者进行约束、规范,否则怎样确保按照架构、设计方案进行开发,就是件灾难性的事情。
Maverick.Net在MVC的呈现上是非常清晰的。也许你会发现,在你的架构中正是需要引入这样的一种思想。不管是哪个子团队,哪一个人,使用这一个框架,就需要理解它的思想,按照它的规范进行开发。
3.2 分析设计思想的转变 MDA
从大的视角来看,用户需求与软件系统之间的边界,就是UI表现层的东西,也就是View。一套系统最终都是通过View向用户,或者是通过接口向其它系统,呈现出它的全部内容。系统开发是对需求的实现,而架构设计则是对需求的抽象。
在小型项目中,可以不需要什么方法论。把握了需求之后,就着重在表现层的设计,与用户确认好之后,以这个为目标把系统开发出来就OK了。这是很直接的一种方式,也是人的思维模式中比较容易接受的一种方式。在企业级应用的架构中,如果在不同程度上以这种方式驱动,或多或少的以表现层为主体,就会妨碍我们的抽象行为,也就是影响到架构的质量。
MDA的思想中,强调的是先从需求中提取Business Model,然后以Model为主体,对业务逻辑进行分析。在框架层面将M-V-C比较清晰的区分开来,有利于在分析设计思想方面向MDA方式的转换。
3.3 简单,而又强大的框架
忘了是在哪里看到的这样一句话。说它简单,因为整个Maverick.Net项目的代码很少,用于实现的主要对象也不多,它仅仅用一个框架性的协议把M-V-C之间作分离,对Model、View、Controller方面并不做任何过多的扩展、处理。所有关于View的呈现方面、Controller的架构方面等,完全交由使用者自行决定。说它强大,因为整个框架的可扩展性相当不错。
但并不是说这么好的一个框架,拿过来用就是了。说它好,可能很大程度上是在指它的思想层面,并不能代表它目前的实现就是最好的或者说非常合理的。随便列举几点:
a) 有考虑多语言的方案,但是没有实现。
b) 它的Controller是基于类的层面配置,这个该如何使用?
方案1:把它的Controller当作Business Facade,所以可能需要为每个Command写一个Controller类,用这个类去整合与Business Model相对应的各个Business Rule类。这种方案有利于分布式、SOA的运用,但是每个Command开发个Controller显得有点繁琐。
方案2:把Controller作为完整的Business Logic封装,在上下文中使用参数等其它机制,将Command关联到Controller的方法层面上。
c) Controller + View类型的选择
方案1:使用aspx类型的Controller。这种方式的优点在于能够使用ASP.Net的机制,包括常规的PostBack事件机制、用户控件等,或者整合ASP.Net其它一些东西,例如Atlas等,可能都会比较方便。但以我个人的观点来看,不提倡将*.aspx.cs作为Controller,而是纯粹的作为View的一部分。*.aspx.cs只负责利用ASP.Net的机制,从页面获取参数、创建Model对象传递给控制类;在从控制类重新得到Model等相关信息之后(Controller处理完毕的结果),再利用ASP.Net机制将这些信息进行呈现。
当然,将*.aspx.cs作为轻量级的Business Facade也是可以考虑的方案。
方案2:使用xml或者Maverick.Net标准的document方式。
其实这两种方式之间的差别还是很大的。使用xml方式的话,将使用XsltTransform进行转换,可能需要编写大量的xslt文件。使用Maverick.Net标准的document方式,是指完全类似ASP的方式了,使用DocumentTransform进行转换。这两种方式都是使用Maverick.Net的典型工作方式,即以.m作为请求页面的后缀,由Dispatcher对HttpRequest进行处理。在页面编写时,它们都不能使用ASP.Net特性了,说这种方式下完全抛弃了ASP.Net机制也不过分。所以对于ASP.Net开发者将它们视为同一种方式也不奇怪了。
直接使用这种方式开发,可能没有多少人能够接受。但是基于这种方式,在View的开发方面下一些功夫,例如封装、通用化模板的扩展、添加适用于Maverick.Net的Ajax方案等,是能够在一定程度上降低这种方式下View开发的繁琐性,达到基本可以接受的程度(相对于ASP.Net开发者而言)。

2006-09-11:补充一副图

Maverick.Net代码解析

相关概念、总体处理流程参考Maverick.Net介绍篇,不再罗嗦。一看就懂的地方略,侧重在貌似疑难之处及部分过程的分析。本人水平一般,如有不正确的地方欢迎指正。
Dispatcher
职责:一,HttpHandler,处理Http请求,包括Http Request-Command的映射、Command对象的管理;二,负责对初始化操作的管理。
ExtractCommandName(HttpContext context)
将类似welcome.m的虚拟请求文件名的.m后缀去掉,取welcome作为Command的名称。这个名称与maverick.config配置中command节点的name属性相对应。
LoadConfigDocument(HttpContext context)
初始化操作函数之一,读取maverick.config配置文件,返回XmlDocument对象。
其中有一个转换处理,读取maverick.config的xml之后,使用一个xsl文件进行转换,返回转换之后的XmlDocument对象。可以这样来理解,Maverick.Net已经确定了配置文件maverick.config的格式,但你可能不是使用这个格式,例如你对Maverick.Net做了扩展,可能也会相应的调整maverick.config格式,等等情况之下你可以用一个xsl将maverick.config配置转换成Maverick.Net要求的格式。
ReloadConfig(HttpContext context)
初始化操作函数之一。
a) 创建Loader对象。Loader创建过程中将创建Command Factory、View Factory、Transform Factory这一系列工厂,并利用这些工厂,根据maverick.config的配置创建所有的Command、View、Transform对象。
b) 对forward类型View的检查。Maverick.Net介绍篇中提到过,forward类型的View是重定向到另外一个Command。这段检查代码就是遍历所有Command下面的View,确保forward类型的View重定向的目标Command是存在的。
c) 创建两个特殊的Command并添加到Command集合中。关于这两个特殊Command的说明参考Maverick.Net介绍篇。
Init()
初始化操作的入口函数。
    Maverick.Net将Dispatcher的IsReusable属性返回true,因此IIS将使用Application Pool重用这个HttpHandler(参考HttpHandler相关文档)。但注意:重用并不是说Init()函数对整个应用而言只会执行一次,IIS在并发处理多个Http Request时,会为每个请求分配一个Http Handler对象(Dispather),每一个Dispather实例将执行一次Init()操作。
ProcessRequest(HttpContext context)
IHttpHandler接口方法,参考MSDN文档。
创建MaverickContext对象时,先尝试从HttpContext.Items中获取,是因为如果将多个Commands配置成一个链(Chain)来处理某个Http Request(就是Action Flow的概念,不过Maverick.Net是在前端控制器-Front Controller上实现这个,直觉上看跟View的关系太紧密,有远离Business Logical/Workflow的感觉,但确实是一个Action Flow),需要使用同一个MaverickContext对象,用于Chain中的各个Command之间协作时传递Model等数据消息。这个Chain中的第一个Command会把MaverickContext对象放入HttpContext.Items中,随后的Command都是从HttpContext.Items中获取。Chain中的Command如何传递数据消息?Chain中某个Command处理时,可能会更新、处理Model信息,还可以向MaverickContext中添加特定的参数,接下来的Command就可以从MaverickContext获取更新处理之后的Model和这些特定的参数。
    ICommand GetCommand(string name)
    名称为*的Command被用作一个特殊的Command,即当Dispatcher接收到一个无效的Command时,将使用名称为*的Command来处理。我们在使用Maverick.Net框架时可以实现这个Command,用于提示用户无效的操作信息。否则将产生一个404的异常。
其它
a) Command大小写敏感选项:处理Command在大小写敏感方面的问题。首先Command对象用Hash Table缓存,使用Command Name的Key值进行索引时存在大小写敏感问题。Command Name通过HttpRequest.ApplicationPath解析出来,某些系统中可能会自动将ApplicationPath转换成大写。因此提供这个功能用于解决大小写敏感问题。
b) 两个特殊的Command:ReloadCommand、CurrentConfigCommand参考Maverick.Net介绍篇。
c) 关于Dispatcher线程安全方面。详细的HttpHandler线程安全方面话题,请参考其它相关资料。
第一点,在初始化的一系列操作中,我们可以看到很多地方将HttpContext对象作为参数传给工厂类,而在Command、View、Transform等执行时刻(函数Go())也用到HttpContext对象,是否会存在线程安全问题?其实在各个工厂类中,以及Command、View、Transform等对象创建时刻,如果使用到HttpContext内容,只是用于获取ApplicationPath等对于整个应用而言全局的数据信息,这在每个Http Request期间都是相同的;而Command、View、Transform等执行时刻使用到的HttpContext对象,都是从MaverickContext中获取,在Dispatcher处理每个Http Request时都会使用当前请求的HttpContext创建一个新的MaverickContext(将Command配置成Chain方式除外)对象。因此对HttpContext的使用上,Maverick.Net不会有线程安全方面的问题。
第二点,ReloadCommand和CurrentConfigCommand这两个特殊Command是非线程安全的。Init()函数说明中提到过,虽然Dispatcher对象是可复用的,但在Application Pool中可能会有多个Dispatcher的实例。当提交一个ReloadCommand时,会从Application Pool中取一个实例用于服务这个请求,因此这个Dispatcher实例会根据当前的maverick.config配置重新进行初始化,但这个初始化不会影响Application Pool中的其它实例,其它实例使用的仍然是根据旧的maverick.config创建的对象。
其实它是违反了HttpHandler的一个线程安全规则:不要使用成员变量或类似的机制,用于不同的请求、线程间保存传递状态、数据信息。这种情况下可以用一个类似Observer模式解决,或者对Dispatcher使用成员变量保存Command对象集合的方式做修改。
  • 初始化、工厂类部分
这部分类图如下:
图一:初始化、工厂部分类图
Maverick.Net实现了2种类型的Transform和6种类型的View,每种类型的Transform和View都对应到一个工厂类,负责创建特定类型的实例。实际的Transform、View的创建操作,永远都是由该类型的Transform、View对应的工厂对象完成的。
MasterFactory负责管理全部的Transform、View的工厂(分别用两个HashTable成员保存),并聚合了Transform、View的创建方法。因此,当需要创建Transform或View对象时,调用MasterFactory.CreateView()或者MasterFactory.CreateTransform(),将请求提交给MasterFactory对象。MasterFactory负责根据Transform、View的类型,找到对应的工厂对象,然后调用工厂创建Transform或View。
ViewRegistry主要负责Global View的注册,以供随时取用Global View对象;以及对全局View的引用问题(就是CreateViewsMap方法中的处理)。ViewRegistry保存了一个MasterFactory的对象,当要创建一个View时,通过调用MasterFactory的方法完成。
关于Shunted View相关概念后面介绍。
Loader负责对全部创建工作的管理。经过上面类职责的了解可以知道,其实Loader只需要管理好CommandFactory、MasterFactory、ViewRegistry这三个类就达到目的了。Loader在构造函数中完成初始化操作,所以Dispatcher只需要创建Loader对象,从Loader获取创建的Command集合就行。
Loader
初始化的执行过程大致如下图所述:
Loader(XmlDocument doc, HttpContext httpContext)
构造函数,完成全部初始化操作。
创建Command的容器HashTable时,将根据Command大小写敏感配置分别进行处理。
SetupCoreModules()
分别创建各种类型的Transform Factory、View Factory,调用这些工厂的初始化方法,将工厂对象注册到viewFactories、transformFactories这两个HashTable成员中。
工厂的初始化方法给工厂对象提供一个初始化自己的机会,但是现有的一些Transform、View的工厂中,绝大部分初始化方法都不做什么实质性的事情。
LoadDocument(doc)
在调用这个方法之前,各类工厂对象均已经被创建并完成初始化操作,这个方法就是利用这些工厂对象,根据maverick.config配置文件的XmlDocument对象(doc),创建各个框架对象。
LoadModules(XmlElement modulesNode)
    这个函数根据maverick.config文件的配置,创建自定义的Transform、View工厂对象,以及自定义的ShuntFactory。
这是为扩展Transform、View的类型,以及实现ShuntView提供的一种途径。假如需要扩展一种新的View类型,就需要为这个View类型实现一个工厂类,将这个工厂配置到maverick.config文件中,这样在初始化的时候将创建这个工厂对象并注册到MasterFactory的Transform工厂集合和View工厂集合中。如果需要创建这类型的Transform或View,将使用到这些工厂对象。
Modules节点配置示例如下: 
<modules>
  <transform-factory type="mytran" provider="mytran.mytranFactory, mytran" >
    <default-key-name value="mytran" />
  </transform-factory>
   <view-factory type="myview" provider="myview.myviewFactory, myview">
     <default-key-name value="myview" />
   </view-factory>
</modules>
MasterFactory
CreatePlainView(XmlElement viewNode)
读取View的类型属性,根据类型值取对应的工厂,使用工厂创建这种类型的View对象并返回。Plain View这个单词意指简单的View,即直接由对应的工厂创建出来的View对象。
CreateView(XmlElement viewNode)
使用CreatePlainView函数得到IView对象之后,再根据这个View是否有配置Transform、参数节点,相应的将对象创建成ViewWithTransforms、ViewWithParams类型,以便在View的执行时刻(View对象的Go()方法内)能处理Transform操作和参数。
CreatePlainTransform、CreateTransform 跟上面两个方法完全类似。
ViewRegistry、ViewRegistrySimple、ViewRegistryShunted
这三个类的关系从图一:初始化、工厂部分类图中可以看出来。
在maverci.config文件中,Command节点下面的View节点可以这样配置:<view name="loginRequired" ref="loginForm"/>,意思是将从全局View中引用id为loginForm的View对象。
这个引用关系并不是在执行时刻处理的,而是在Maverick.Net初始化过程中完成。执行时刻处理可以这样描述:如果Command根据Controller执行结果,发现需要执行名称为loginRequired的View,而这个View是对Global View中id为loginForm对象的引用,所以从Global View中检索出这个loginForm的View对象,然后执行它。这样的处理方式,给框架带来复杂和不规范性,而把这个操作放在初始化中,则所有View的执行处理就统一起来了。Command对象聚合了一个或多个View对象,在为Command创建聚合的View或View对象集合时,如果某个View是对Global View对象的引用,则从Global View中检索到这个View对象,直接返回给Command,避免Command在执行时刻从Global View中检索。这就是CreateViewsMap函数的作用。
Global View的创建是在创建Command之前完成的,确保了在Command创建时刻能够引用到Global View对象。Global View的引用关系在初始化时刻已经被处理掉,因此ViewRegistry、ViewRegistrySimple、ViewRegistryShunted这三个类也只在初始化的过程中用到,在Maverick.Net正常的处理Http Request期间已经不再需要使用。
ViewRegistryShunted类用于在完成上述功能时,对Shunted View特殊处理的支持。
  • Command     
Command承担的工作很简单:执行Controller的处理,根据返回的结果,选择对应的View,运行这个View。
Command分为两类,一类是没有Controller的,对应CommandSingleView。这类Command不需要根据Controller的执行结果选择View,因此,这类Command有且只能有一个View,Command的执行其实就是直接对这个View进行呈现。Maverick.Net对这类Command使用一个NullController,这个Controller不做任何事情,只是为了保持Command行为的一致性。
另一类是有Controller的,对应CommandMultipleViews。在初始化创建Command实例时,已经为Command创建了Controller对象,因此Command只需要调用这个Controller对象的Go()方法执行处理。
CommandBase提供了Command规范性的处理。CommandSingleView和CommandMultipleViews继承CommandBase,主要实现GetView方法,即怎样从Command对象持有的View对象中选择正确地View来执行。
  • View
几个简单的View。
NullView:不做任何事情。可以用于Command节点下面没有配置任何View的情况,但Maverick.Net目前没有这样使用,如果Command下面没有View节点将产生异常。
TrivalView:将Model以字符串的方式直接输出。如果某个Http请求的结果,只是一段文本消息,或者是输出一个文本文件、xml文件内容等,可以使用这种类型的View。
ForwardView、RedirectView:功能上基本是一样的。
ForwardView重定向到另外一个Command。它是直接在View的执行时刻,使用Dispatcher获取Command对象,调用Command.Go()方法。假如某个View需要重定向到名称为login的Command,View节点配置为:<view name="viewName" command="login"/>。
RedirectView使用Response.Redirect()方法进行重定向,所以重定向的目标可以是某个asp/aspx页面也可以是某个Command。假如需要重定向到名称为login的Command,RedirectView的实现方式为Response.Redirect("login.m"),这种情况下可以将ForwardView看作是RedirectView的一种特例。RedirectView更倾向于重定向到某一个asp/aspx页面,因为这类型的View在重定向之前,会将需要传递的参数,以GET的方式拼写到url中,参数将包括IDictionary类型的Model对象和MaverickContext.Params。如果Model对象是String类型,则RedirectView将把Model当作目标url,而忽略view节点中配置的url。这类型的view配置示例如:<view name="viewName1" path="login.m"/>、<view name="viewName2" path="default.aspx"/>。
如果整个项目以Maverick.Net架构,是不需要使用RedirectView重定向到另外的asp/aspx页面的,提供这种类型,有利于与其它项目进行基于页面层次的整合,或者类似的用途。
DocumentView:这个View类型复杂些。
在类图结构中,最基础的是DispatchedView,这个类型的View完成asp/aspx等类型的页面文件转换成HTML的操作,它是internal的类型,只在框架内部使用,实现的也是document类型View最基础的功能。
DocumentView聚合一个DispatchedView对象,因此拥有DispatchedView的功能,派生DocumentView主要是解决Model存放位置问题。它为abstract类型,通过abstract类型的方法SetAttribute提供给子类解决如何传递Model。
ApplicationDocumentView、SessionDocumentView、RequestDocumentView都继承自DocumentView,实现自己的SetAttribute方法,分别使用Application、Session、HttpContext.Items作为存放Model的地方。Type为document类型的View,在配置节点中通过scope属性确定该View属于这三种类型中的哪一种,如果没有声明scope属性,默认为RequestDocumentView类型。
以一个RequestDocumentView类型的对象来看,在调用这个view的Go()方法的时候,先在DocumentView.Go()方法处判断,如果Model对象不为null,则执行RequestDocumentView.SetAttribute()方法,将Model放入HttpContext.Items中。这样,这个view对应的aspx页面的服务器端代码,就应当从HttpContext.Items中获取Model对象进行显示。接下来将调用聚合的DispatchedView对象的Go()方法,将这个aspx页面解析成HTML。
XmlSerializingView:单纯的从View的功能上看,它也非常简单,就是将Model反序列化之后的xml直接输出。这类型的View大都用于基于xml的网站方案中,将Model序列化成xml之后,可以使用XsltTransform将其转换成HTML。
以上介绍的几个类型的View的实例都由相应的View Factory创建,可以说是一种简单类型的View对象。它们实现了几种基本形态View的功能,Document类型的View复杂一点,在View的显示过程中将使用到Model对象,所以Document类型的View在类的结构上看起来也复杂一些。
剩下的两种类型的View就当作扩展类型来看待吧。
ViewWithParams:有时候我们可能希望为某些View配置一些特殊的参数,例如有两个功能,Web页面98%都是相同的,仅有微小的一点区别,我们可能会希望只写一个页面,通过给它们不同的参数,使这一个页面运用在两个功能中。ViewWithParams聚合了一个IView对象,它在本身的Go()函数中附加一些对参数的处理,即将参数保存到MaverickContext这个上下文对象中,然后再调用聚合IView对象的Go()方法。这样,被ViewWithParams封装过的IView对象或者是对应的aspx页面中,就能通过MaverickContext访问到这些参数。
参数的配置示例如下:
<view name="UserView" path="UserView.aspx">
  <param name="param1" value="???" />
  <param name="param2" value="???" />
</view>
因为这种类型的View只是对基础类型View的一个封装扩展,并且也许会有多个类型的View希望能够使用参数,复用这样一个功能特性,所以Maverick.Net并没有把这种封装过程放入到具体的某一个ViewFactory中,而是放在了MasterFactory中处理。在MasterFactory.CreateView(XmlElement viewNode)方法中可以看到这一处理。
ViewWithTransforms:这类型的View聚合一个IView和多个ITransform对象,用于在View执行时对Transform处理。跟ViewWithParams类似,用ViewWithTransforms封装IView对象的操作也是在MastrerFactory中处理。在Maverick.Net中,虽然从表面上看起来View和Transform的衔接很紧密,但Maverick.Net对View和Transform采用一种较松散的耦合方式来处理。
在现有的View类型中,只有基于DocumentView类型的以及XmlSerializingView类型才能使用Transform操作。
ViewWithTransforms.Go()方法先将Transforms对象放入MaverickContext中,然后调用聚合的IView对象的Go()方法。聚合的IView对象为DocumentView或XmlSerializingView类型,对于DocumentView类型的将调用DispatchedView.Go()方法,在DispatchedView.Go()和XmlSerializingView.Go()方法中,都会通过MaverickContext.NextStep对象间接使用到Transform对象,逐步完成输出内容的转换。
以一个DocumentView对象、具有一个Transform配置节点作为示例,看一下Transform的处理过程。
每一个Transform对象都由位于它前面的一个驱动对象创建一个ITransformStep,用于执行转换操作。第一个Transform的ITransformStep由DispatcheView创建。
DispatcheView执行时,先使用第一个Transform对象创建ITransformStep,使用Server.Execute()将DispatcheView的aspx页面转换成HTML,放入ITransformStep创建的MemoryStream中,然后调用ITransformStep的Go()方法。随后的每一个ITransformStep在Go()方法中,首先使用下一个Transform对象创建下一个ITransformStep,然后从MemoryStream读取上一个驱动对象放入的HTML,用它对应的Transform配置节点中的key属性值作为Key值,将HTML放入HttpContext.Items中,最后使用Server.Execute()方法,执行它对应的Transform对象的aspx页面,将输出的HTML放入下一个ITransformStep的MemoryStream。
当执行达到最后一个Transform位置时,它后面再没有其它Transform,因此创建LastStep类型的ITransformStep对象,这个对象的Go()方法是一个空操作,但是它将HttpContext.Response.OutputStream作为MemoryStream的替代,因此,最后一个Transform的aspx页面在使用Server.Execute()方法执行后,就直接输出到Response.OutputStream中了。
理解上面的处理过程之后,会有一个疑问,就是只看到了最后一个Transform对应的aspx执行后的HTML代码发送给客户端,它之前的Transform对应的aspx页面、View对应的aspx页面执行后的HTML代码呢?从这个过程中我们只能看到这些HTML代码被依次放入HttpContext.Items中,并没有看到向客户端输出。实际上,从Maverick.Net的示例项目Friendbook中,取一个View最后一个Transform相关联的aspx页面看一下就知道了,在这个aspx页面里,会看到类似<%=Context.Items["wrapped"]%>的服务器端语句,就是这个语句从Context.Items中取出一段HTML内容并输出。这样就可以明白了,最后一个Transform对应的aspx页面执行后的HTML代码,自然就包括了它前面的Transform、View执行后的HTML。
View对象本身的关系可能会有点复杂,例如可能创建的一个ViewWithTransforms对象,将会聚合一个RequestDocumentView,而这个RequestDocumentView又聚合一个DispathedView。另外,在View的执行时刻,跟Transform的转换结合在一起。所以,一个IView对象的Go()方法,感觉上转来转去,使对这个处理过程的理解产生疑惑。通过上面的描述,好好的理解这些对象的职责,把它们的关系梳理清楚之后,你会发现其实还是很简单的。
ShuntedView
整体上来看,ShuntedView也是比较简单的。普通类型的View,通过一个name属性作为Key值,ShuntedView则必须使用name属性加上一个mode属性一起才能作为Key值。这是用于实现多语言之用,比如同一个View UserQuery,可能需要有中文、英文等语言支持,将语言代码作为Mode,使用UserQuery + ch获取中文版本,使用UserQuery + en获取英文版本。相应的,View的配置节点类似如下:
<command name="UserQuery">
    <controller class="" />
    <view name="loginRequired" ref="loginRequired"/>
    <view name="success" type="document" path="UserQuery-ch.aspx" mode="ch">
        <transform path=""/>
    </view>
    <view name="success" type="document" path="UserQuery-en.aspx" mode="en">
        <transform path=""/>
    </view>
</command>
Maverick.Net在多语言的实现方式上并不理想。一个View,如果对应于每一个语言都需要写另外一个页面,这种方式只能说太笨了。这种情况下,如果View需要修改,任何时候都将需要对不同语言版本的页面同时修改。
下面这副图帮助理解一下Shunt处理过程。
目前Maverick.Net并没有使用ShuntedView,在Loader的构造函数中,创建的是ViewRegistrySimple而不是ViewRegistryShunted对象。如果使用多语言,应当在这个地方使用ViewRegistryShunted,上面的图就是基于这种情况下的处理过程描述。
具体的实现细节根据这个序列从代码上可以看出来,下面讲的是大致的思路。ViewShunted仅仅是聚合一个IShunt对象,并实现IView接口,真正的操作是由IShunt对象完成的。在多语言的运用情况下,IShunt对象为LanguageShunt类型,这个类型的对象维护了一个HashTable成员modes,它以View配置节点的mode值作为Key,将name相同而mode值不同的多个IView对象保存在这个HashTable中。这样,name相同而语言版本不同的多个IView对象,通过ViewShunted的封装,从外部看起来它就成为一个IView对象了。在上面的xml示例配置中,名称为UserQuery的ICommand对象,将拥有两个(注意不是三个)IView对象,一个为loginRequired,另外一个为ShuntedView类型的success。这样对于ICommand对象执行时,对ShuntedView的处理方式上也是规范的,ICommand对象无需了解后面的细节。ViewShunted类型的View在执行时,尝试从Request.Headers对象中取语言代码,然后根据这个语言代码获得对应的IView对象,继而调用这个IView对象的执行方法。
  • Transform
其实经过上面对ViewWithTransforms执行过程的理解,对于Transform对象也就没有什么悬念了。
尽管DocumentTransform在概念上理解起来比较牵强,因为view的aspx执行之后就是HTML了,感觉上不再需要什么转换处理,Maverick.Net的示例项目Friendbook中将Transform作为几乎所有工作页面的一个整体包装页面的用途。也许你还能发现其它有意思的用处。
对于基于xml的网站,XsltTransform的作用理解起来就非常直接了。对XsltTransform,有几种方式的用法:
1. 将View配置成document类型,用path直接指定一个静态xml文件,然后使用xsl解析。例如
<view type="document" path="welcome.xml">
  <transform path="outside.xsl"/>
</view>
2. 将View配置程xml(XmlSerializingView)类型,把Model反序列化成xml,使用xsl解析。
3. 将View配置程trivial类型,由Controller直接构造xml,然后用xsl解析。
4. 将View配置成xml类型,View并不输出任何xml,直接使用Transform处理。这种情况通常第一个Transform输出xml,后面的Transform解析成HTML。这种跟1基本完全一样。
对xml类型的View使用XsltTransform转换的处理过程,跟DocumentTransform完全一样。
在这个处理的Chain上,IView对象是起点。前面提到过,View跟Transform之间是一种较松散的耦合方式,这样,IView对象无需去管理自己有多少个Transform,应该怎样一步一步的执行转换,以及将最终的结果发送给客户端。IView对象只需要完成自己的职责,它知道在它的后面一定有其它的对象来处理上面这些事情,所以IView处理完之后,将它输出的视图代码抛给下一个对象即可。
LastStep对象是Chain的终结者,负责将最后的输出发送给客户端浏览器。
大概考虑到无论是Server.Execute()或者System.Xml.Xsl.XsltTransform.Transform()方法都可以向Stream直接输出,Maverick.Net在Chain处理上,采用一个Stream类型的成员变量向后传递输出内容。既然这样,在Chain上每一个执行步骤中动态创建一个ITransformStep就成为一种必要。
  • Controller 
Maverick.Net提供一些Controller的基础类,用于实现不同用途下的Controller。
    ......

posted

zhuan.gif MAVERICK.NET初窥

ASP.NET出来了很久了,微软一直强调其ASP.NET是给WEB开发带来了很多的方便,code-behind的方式优化了代码的结构,等云云。然而当我们真正用ASP.NET来开发时,我们发现我们还是陷入到了混乱之中,如:

  • MVC如何实现(虽然code-behind从某种意义上说是实现了C和V的分离,但是还是远远不够)
  • 页面间的flow如何处理(还是在代码中采用了hard-code的方法,要修改页面的跳转,必须修改源代码,页面间的关系不能清楚地表现和可配置)
  • 多语言怎么实现(虽然可以用资源文件来解决,但是一个单词的中文和英文的长度不一致,很多时候还是要重写2个页面,根据不同的浏览器多语言配置如何实现这些不同页面的选择,也不能很好解决)
  • 公用页面模块如何尽量提高可复用性(虽然微软也提供了User Control,但是它始终和ASPX有很多不一样,很难对2者共同对待)

微软也意识到了这些问题,于是出了个UIPB,但是UIPB也仅仅解决了一小部分的问题,如前面所说的页面跳转的问题。坦白地说,我认为UIPB的设计初衷实在是一个错误的方向。UIPB主要解决一个项目WEB表现层和WINFORM表现层如何能够最大范围的复用的问题。试问有多少项目会有这样的需求?诚然有些项目确实需求2种表现层,但是也是各自完成不同的任务居多。即使是有这种需求,我们也知道WINFORM和WEB有太多的不同,有些WEB中多个页面跳转完成的事情,在WINFORM中仅仅是一个窗口就可以完成。另外WEB中要尽量少用弹出窗口,而WINFORM没有这种限制。因此我认为UIPB还是没有解决WEB层的大多数问题,不能适应现在商业的WEB项目开发。

那么我真的开始苦恼了,.NET项目中应该用什么来实现表现层?看看J2EE阵营,他们确实也苦恼,但他们苦恼的是面对那么多的开源解决方案应该选择那个。有的时候真想改姓J2EE算了,呵呵。

终于前些天看到了MAVERICK.NET项目,实际上这个项目也是从J2EE的MAVERICK项目port过来的。我把Maverick.NET当了下来,研究了几天,总算心理稍稍平了点。从我现在对MAVERICK.NET的浅薄的了解中,我认为它至少解决了以下几个问题:

  • 完全的MVC实现
  • 页面间的跳转问题可以通过一个统一的配置文件建立期间的联系
  • 页面模块可以灵活地通过配置文件plug到多个View中去
  • 多语言的幽雅实现
  • 页面的模板可以使用XSLT等转换技术

下面我从MAVERICK.NET中自带的一个简单例子来简单说明:

  1. 配置IIS,将.m的文件用ASP.NET引擎来解析
  2. 配置web.config,加入以下语句:
    <configSections>
        <sectionGroup name="Maverick">
            <section name="Dispatcher" type="System.Configuration.NameValueSectionHandler,system, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, Custom=null" />
        </sectionGroup>
    </configSections>

    <Maverick>
        <Dispatcher>
            <add key="configFile" value="maverick.config" />
            <add key="currentConfigCommand" value="currentConfig" />
            <add key="reloadCommand" value="reload" />
            <add key="limitTransformsParam" value="maxTransforms" />
            <add key="commandCaseSensitivity" value="insensitive" />
        </Dispatcher>
    </Maverick>

    </system.web>       
        <httpHandlers>
            <!-- 该工程的任何页面访问都会首先执行该httpHandler,实际上是一个dispatcher,
                来根据maverick.config的配置来启动相关页面 -->
            <add verb="*" path="*.m" type="Maverick.Dispatcher, Maverick" />
        </httpHandlers>
     </system.web>

  3. 配置maverick.config文件
    <maverick version="2.0" default-view-type="document" default-transform-type="document">
        <commands>
            <command name="Default.aspx">
                <view name="success" type="trivial">
                    <transform path="~/Wrapper.aspx"/>    <!-- 这就是一个标题,也就是嵌入的公用页面模块 -->
                </view>
                <view name="april" path="~/April.aspx">
                    <transform path="~/Wrapper.aspx"/>
                </view>   
                <view name="button" path="~/Button.aspx">
                    <transform path="~/Wrapper.aspx"/>
                </view>
            </command>
        </commands>
    </maverick>
     
  4. 建立Default.aspx文件,在code-behind文件中添加一个以下方法
    protected string viewName = "success"; // 默认显示名为“success”的view
    public override string Go(Maverick.Flow.IControllerContext cctx)
    {
        return this.viewName;
    }

    在一个button的时间代码中添加如下代码,当button按下时,就会根据maverick.config的配置来显示相应内容
    private void Button1_Click(object sender, System.EventArgs e)
    {
        this.viewName = "button"; //按下button后就会显示名为button的view
    }

    在一个日历控健的Selection_change事件代码中,添加如下代码
    private void Calendar1_SelectionChanged(object sender, System.EventArgs e)
    {
        if (this.Calendar1.SelectedDate.Month == 4)
            this.viewName = "april";    // 当该代码执行时,就会转向名为april的view
    }
     

  5. 建立April.aspx、Button.aspx、Wrapper.aspx页面

以上的例子至少体现了,Maverick的页面跳转配置实现、公用页面模块灵活配置的2个优点,因为这是一个最简单的实现,所以其他的特点没有完全展现。关于MAVERICK.NET的应用、以及under the hook,我将在后续的post中描述。

转载于:https://www.cnblogs.com/lsgoodsun/archive/2007/08/13/854172.html

Maverick.Net介绍 (来自http://www.cnblogs.com/RicCC/archive/2006/09/17/506890.html)相关推荐

  1. IO多路复用之epoll总结 http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

    IO多路复用之epoll总结 http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

  2. http://www.cnblogs.com/chio/archive/2007/09/10/888260.html

    一. 虚析构函数 我们知道,为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数.因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数. 如: cl ...

  3. C# 中的委托和事件[转自http://www.cnblogs.com/jimmyzhang/archive/2007/09/23/903360.html]

    PDF 浏览:http://www.tracefact.net/Document/Delegates-and-Events-in-CSharp.pdf 文中代码在VS2005下通过,由于VS2003( ...

  4. windows phone 7 中文天气预报应用--来源http://www.cnblogs.com/liulunet/archive/2011/08/17/2141696.html...

    windows phone 7 中文天气预报应用 wp7的应用还是太少了,中文应用更少.虽然有天气预报应用但是自己感觉并不好用,感觉这样的程序应该很简单,于是萌生了自己写一个的想法. 印证了群里朋友说 ...

  5. http://www.cnblogs.com/wayfarer/archive/2004/09/29/47896.html

    动态添加程序集 转载于:https://www.cnblogs.com/lovey/archive/2013/04/07/3006112.html

  6. 基于dsp_builder的算法在FPGA上的实现(转自https://www.cnblogs.com/sunev/archive/2012/11/17/2774836.html)...

    一.摘要 结合dsp_builder.matlab.modelsim和quartus ii等软件完成算法的FPGA实现. 二.实验平台 硬件平台:DIY_DE2 软件平台:quartus ii9.0 ...

  7. 归并排序(转载http://www.cnblogs.com/jillzhang/archive/2007/09/16/894936.html)

    归并排序是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为: 1)划分子表 2)合并 ...

  8. SQL视图(转自http://www.cnblogs.com/fineboy/archive/2005/09/14/236731.html)

    SQL Server 视图 视图在数据库开发过程中是非常重要的,对提高查询速度有很大的提高.因此我们的学会创建视图,并且有效的使用视图. (1)表准的SQL视图         标准视图比较简单,大家 ...

  9. 字典树模板及讲解 http://www.cnblogs.com/tanky_woo/archive/2010/09/24/1833717.html

    Tire的数据结构定义: #define MAX 26 typedef struct Trie { Trie *next[MAX]; int v; //根据需要变化 }; Trie *root; ne ...

最新文章

  1. 【数字孪生】关于数字孪生的冷思考及其背后的建模和仿真技术
  2. 每日一皮:这一定是“宅”的极致了...
  3. vs2010中编译zint的问题
  4. 地图画指定区域_零基础学CAD绘制一张桌子为例,使亲们更好地熟悉三维绘图环境...
  5. Scala语言整理(一)
  6. mysql读取和写入的峰值_计算MySQL的内存峰值公式
  7. 经典的java程序_Java经典程序
  8. mysql执行计划查看_查看Mysql执行计划
  9. c++ 传智课件_沪科版初中物理九年级全册第二节 科学探究:物质的比热容公开课优质课课件教案视频...
  10. SparkSQL默认存储格式入门
  11. 龙芯2k1000-pmon(5)- pmon无法修改环境变量的问题
  12. 杭州师范大学仓前校区宽带认证客户端电信用户拨号宽带链接路由器
  13. React SSR渲染
  14. 位操作的应用实例(2)位掩码
  15. 数据库应用技术课程设计之商城管理系统
  16. Xenu Link Sleuth死链接检测工具(SEO工具)
  17. 全面认识痛风:症状、风险因素、发病机理及管理
  18. chrome常用扩展程序汇总(程序员版)
  19. 泛泰binx和ota升级包下载工具Android版[2013.6.7提供源代码]
  20. 赛车游戏java_Java 赛车游戏

热门文章

  1. 如何push一个docker镜像到DockerHub上
  2. deeplearning4j
  3. 操作系统02进程管理Process_Description_and_Control
  4. axios异步请求数据的简单使用
  5. ACM-ICPC北京赛区2017网络同步赛H
  6. hiveql函数笔记(二)
  7. 查看Linux上程序或进程用到的库
  8. android自动化框架简要剖析(一):运行原理+基本框架
  9. 国内ios分亨组件,
  10. C语言程序设计 文件操作函数