Asp.net2.0轻量级模板开发架构和完整demo
使用模板在php和java里很普及。微软官方没有给我门提供类似php模板的工具类。应为aspx页面本身就是模板。控件也是模板,并且微软官方提供的接口之丰富,功能支持之多使他们没有必要在提供类似php模板之类的工具类库。
标准的aspx页面也是可以实现MVC的。但是有的时候会觉得MVC功能实现的不是很彻底。比如一个程序员和一个美工结合。可以对页面进行逻辑和外观上的改版。但是一个美工自己是只能修改外观布局。逻辑功能暴露给美工的控制接口太少了。经管有控件的属性。还有<% = Value %> 这样的语法可以暴露在aspx页面上。但是有多少美工熟悉aspx各个控件的属性那。<% = Value %> 这样的语法只是输出结果,对内部逻辑是没办法控制的。即使用<% =函数(参数) %> 这样的语句,修改参数。也不是完善的解决方法。 所以模板类第一个需求是让MVC彻底一些。把逻辑功能更多的开放给美工。
WEBFORM框架设计的很优秀,里面功能很多,结构复杂。如果我对一种特定的web请求,只抽出WEBFORM框架内我需要的一部分功能。而舍弃对这次请求无用的功能。那么服务器对web请求处理的速度肯定会大幅度提高。这样模板类第二个需求是想尽一切办法提高对web请求的响应速度。
结合上面两点,自己设计了一个轻量级的web开发架构。让页面都通过模板来生成。并设计了对应此种模板的适合的使用方法。类似功能的代码在google上一搜一堆。但使用自己开发的web开发架构,用起来很习惯。开发速度快上了不少。一个人用这种架构,很轻松的完成了一个原先看来需要几个人在相同时间才可以完成的项目。现在写了一个完整的使用demo,里面有我习惯的使用方法。可以让大家更好的了解使用方法。同时也是我门公司新员工的培训demo。下载地址 https://files.cnblogs.com/vitality2007/WebSite_Demo_Down.rar
我门先从速度出发,aspx的低层有很多文章在谈。对来访的web请求最先处理的是http处理模块,然后是http处理程序。想要做快速响应,就是放弃WEBFORM框架不必要的部分。优化处理必要的功能。以前很多文章都介绍了一个方法,就是自己实现http处理程序。我门现在也使用这个方法。自己实现http处理程序。当并发很大的时候,异步页面是一个很好的解决方法。于是就象url重写一样。我门让一些对aspx的请求。直接转到模板类的异步http处理程序上。在通过自己的处理类来实现一个页面的请求处理。
在单内核单cpu的机器上对web请求使用异步处理模式决不是一个好注意。现在的模板类是要在特定的cpu上,多内核或多cpu才可以发挥最大性能的。不过对web服务器来说,这个要求一般是能达到的。通过CLR的线程池来执行异步处理。尽可能减少创建线程和销毁线程的开销。
这里在说一点.对http处理器类只要继承AsynchRunTimeTemplet类就可以获得异步处理功能,但毕竟创建一个线程是要消耗一定cpu,内存资源的。所以只要让http处理器类继承RunTimeTemplet类,就可以获得同步处理功能。在同步和异步之间的选择是很难有定论。当页面处理逻辑小,站点并发底的时候。应该使用同步处理程序。当站点并发高,页面逻辑处理多的时候。应该选用异步处理程序。所以决定某个模板处理类是同步执行还是异步执行需要你综合自己服务器的cpu,内存,带宽,web请求并发数量。页面处理逻辑大小.。做动态改变。
对http处理器类提供两个基类 AsynchRunTimeTemplet 和 RunTimeTemplet ,分别提供 异步 和同步 处理支持。是有点麻烦的,不如合成一个可以同时支持同步和异步的基类。这样做可以实现,但是在每一个web请求到来的时候,都要经理很多处理,判断。后来为了速度,我就去掉这个功能,就提供对同步处理和异步处理两个类。当然可下载的demo内所有的处理都是异步处理的。
有的时候直接在代码里获得一个模板文件的解析结果,在通过<% = Value %> 或<asp;label />某种方式在页面上显示是很有必要的。所以这个http处理程序内部要有一个切入点。可以通过函数调用的方式来模拟一个web请求的来访。直接返回模板页面的执行结果。但这个过程是同步来执行的。所以在代码级别提供两个函数
//直接获取 /Templet/List.htm 模板文件的解析结果
ShowHtml = TempletHandler.GetFileString("/Templet/List.htm", TempletHandler.GetRunTime());
//这个函数的意义是 获取/Templet/List.htm模板的解析结果 写到/CreatPage/List.shtml 文件内 , 同步执行
TempletHandler.WriteFile("/Templet/List.htm", "/CreatPage/List.shtml", TempletHandler.GetRunTime());
这样在传统的aspx页内使用模板类,或者生成静态页的需求都完美的解决了。
在模板级提供两个系统标签
{$Include:/Templet/Bottom.htm::缓存$} 直接包含一个静态页面,
{$ExecInclude:/Templet/ExecInclude.htm$} 直接包含一个模板页的解析结果。实现aspx可以包含动态页的效果。但我想这实用的意义可能不大。
这两个标签名是固定名字,直接用就可以,不需要写自己定义解析函数了。到这我就得说一下,模板架构是建设好了,但是提供的系统函数真是少。现在才有两个 Include 和 ExecInclude 。并且我没有在模板页上加上一些变量定义,bool判断的功能。还把以前的loop循环功能也去掉了。应为我觉得,使用这个类库。所有的显示。都可以在对应的标签解析函数内解决。开发设计他是为程序服务的。不是给美工,编辑服务的。是为了提供更好的性能。快速开发。当然,编辑美工可以修改标签的参数,作到控制。
demo包含5个项目,首先看LogicLayer 逻辑层 我定义了两个类, TempletHandler 类用来实现http处理器,SearchTemplet类用来为来访web请求和各个模板处理类做匹配。先看实现http处理器的TempletHandler 类,这个类本身没有多少具体代码,只要继承AsynchRunTimeTemplet 类。和实现IHttpHandlerFactory, IRequiresSessionState接口就可以。
有了这个类以后,下面就可以在配置文件内加上
<add verb="*" path="/WebSiteDemo/Page/*.aspx" type="LogicLayer.TempletHandler,LogicLayer"/>
</httpHandlers>
说明在我门的demo里,所有对page目录下的请求,都转为模板类来处理。在看一下查找模板类 SearchTemplet 。
文章列表页是List.aspx 我门看看他的代码
{
//直接返回一个模板文件的解析结果,在页面上显示,同步执行
string ShowHtml = string.Empty;
//直接获取 /Templet/List.htm 模板文件的解析结果
ShowHtml = TempletHandler.GetFileString("/Templet/List.htm", TempletHandler.GetRunTime());
this.Response.Write(ShowHtml);//在页面上显示出来
//这个函数的意义是 获取/Templet/List.htm模板的解析结果 写到/CreatPage/List.shtml 文件内 , 同步执行
//TempletHandler.WriteFile("/Templet/List.htm", "/CreatPage/List.shtml", TempletHandler.GetRunTime());
}
所有的逻辑处理 分页 什么都没有 全部封装在 TempletHandler.GetFileString 函数内,看代码
BackObject = new ArticleTempletList();
这说明对模板文件 /Templet/List.htm 的处理类是 ArticleTempletList 类 。对某个模板的处理类 都必须要继承AsynchBaseTempletHandler 类, 为了继承了这个类,只要自己实现两个抽象函数,Page_Load,和Page_Init 就可以。ArticleTempletList类如何实现分页。如何显示数据,在这里就不详细的说明。大家可以下载demo看看。我要说明的是ArticleTempletList类的执行步骤。
protected override void Page_Load(ref TempletReplet objILabel, HttpContext _context)函数首先执行,并且每次都执行,在执行他的时候,如果是第一次执行,在内部调用protected override void Page_Init(ref TempletReplet objILabel)
函数,如果不是第一次执行,就不调用Page_Init函数。
Page_Load 执行完毕以后,按模板上的标签排列的先后次序,依次执行各个标签的影射函数,也就是 标签函数执行体 内的函数。所以标签的影射函数有一个相互依赖的关系。先是执行获取数据和数据绑定函数,在执行显示一行的函数,应为有12个标签,所以执行了12次,最后在执行显示分页的函数。到这得说一下原先定义为系统的loop标签
{$loop(3,0,alter)$} {$/loop$}
{$BlockItem$} {$/BlockItem$} :默认的循环行标签对
{$BlockAlterItem$} {$/BlockAlterItem$} :交替循环行标签对
{$BlockPatch$} {$/BlockPatch$} :默认补充行标签对
{$BlockAlterPatch$} {$/BlockAlterPatch$} :交替补充行标签对
现在已经被取消了,应为进入2.0以后。普遍使用范型对象,用dt传数据远没有使用范型集合好。所以取消了loop循环相关的代码,也取消了识别
1。 dt[5][name] : 表示取Datatable内5行name列上的数值,第一个为数字,第二个是列名。
2。 dt[name] : 在loop循环外表示取Datatable内0行name列上的数值。
这样特殊表示的定义。并且用现在的方法可以获得更灵活的显示方式,更快的列表显示方式。具体看 ArticleTempletList 类和/Templet/List.htm 模板。
标签{$数据绑定:12::缓存$}。这里决定一页获取12条数据,那么下面放上12个 {$显示一行$} 标签就好了 数目不要错。要是想改变分页数量 只要上下的数量一致就可以。
{$显示一行:<tr bgcolor='#FFFFFF'><td align='center'><img src='/Images/p1.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#EFF0DB'><td align='center'><img src='/Images/p2.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#FFFFFF'><td align='center'><img src='/Images/p3.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#EFF0DB'><td align='center'><img src='/Images/p4.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#FFFFFF'><td align='center'><img src='/Images/p5.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#EFF0DB'><td align='center'><img src='/Images/p6.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#FFFFFF'><td align='center'><img src='/Images/p7.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#EFF0DB'><td align='center'><img src='/Images/p8.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#FFFFFF'><td align='center'><img src='/Images/p9.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#EFF0DB'><td align='center'><img src='/Images/p10.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#FFFFFF'><td align='center'><img src='/Images/p11.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
{$显示一行:<tr bgcolor='#EFF0DB'><td align='center'><img src='/Images/p12.gif' /></td><td>#ID#</td><td>#Title#</td><td>#Time#</td></tr>:20::缓存$}
20表示标题长度是20 每个标签都要求缓存。这样我门可以精确控制到每个具体的行,每个行的显示长度。将具有更大的灵活性。
在说一下标签介绍:
在成对特殊符号{$ 和 $} 之间的内容为标签
如
{$标签名:参数1:参数2:参数3::字符10:缓存$}
{$标签名:参数1:参数2::字节10$}
在标签内部通过 :: 符号把标签分为 标签参数部分 和 命令控制部分 两块
{$标签名:参数1:参数2:参数3::字节10$}
标签参数部分 :: 命令控制部分
在这两部分内以:号分割标签参数部分和命令控制部分
标签参数部分: 第一个为标签名,就是标签解析函数的对应名称,以后的为函数的参数,标签解析函数接受全部参数类型为string类型的,参数个数为0--20个的函数,返回类型为string类型.命令控制部分: 现在只有两个长度命令符号 字符 和 字节 ,后带一个数字参数,表示截取标签解析结果的长度。一个缓存标记,表示标签参与缓存机制。完整的标签表示为 {$标签名:参数1:参数2:参数3::字符10:字节10:缓存:输出$} 。 输出表示强制发送输出缓冲中的内容,一般不需要使用这个。但是显示过长的内容的时候,有时需要。
最后说一下缓存,原先只是缓存解析数据。现在不但可以缓存解析数据,还可以缓存执行结果。缓存执行结果我用cache保留。是为了在内存压力过大的时候自动释放。有一个特殊的标签 {$ControlCache:缓存1:类型global:时间30:数量200$} 他必须是第一个出现的标签,并且名字叫ControlCache ,有四个参数 ,参数顺序必须按规定排列。才可以。我把他的语法规则都定死了,错一步也不可以。是为了自己不写很多的检测代码。也算是一个偷懒的方法吧 :)
缓存1 | 参数为1表示起用执行结果缓存 参数为0 表示不起用执行结果缓存 |
类型global | 参数为global表示缓存的是整个页面 只要不是global,是其他不为空的数据表示缓存的是一个个的动态标签 |
时间30 | 表示要缓存的秒数 |
数量200 | 表示只接受200个不同的url上的参数的缓存请求,多出的将不在缓存,是为了节省内存,让你可以更好的控制 |
{$标签名:参数1:参数2:参数3::字符10:字节10:缓存$} 这表示在局部页面缓存的时候,这个标签参与缓存。看文章正文显示模板页 /Templet/ShowArticle.htm 他的第一个标签{$ControlCache:缓存1:类型非global就可以:时间30:数量200$} 表示本页执行的是局部缓存,应为我假设需求里文章浏览量是不可以模糊的,必须每次都真实获取到。所以只能用局部缓存。下面的 {$Title::缓存$} 获取头 {$Content::缓存$} 获取正文,和获取数据标签{$数据绑定::缓存$} 一样全部缓存上了。但是标签 {$BrowerCount$} 获取浏览量不参与页面局部缓存,这个标签影射的函数必须每次都执行,获取到精确的浏览量数据。具体请看ShowArticleTemplet 类
有个不好的实现是在缓存数量处理上,控制缓存数量是必须的,但规则是先访问的先缓存,等缓存数量满了以后就不在接受缓存请求,这样的算法实现起来是最简单的。遗憾的是现在没有实现查找浏览量最大页面参数的算法,也没有按浏览量自动更新缓存的算法。要想实现上面的功能,我的思路是做一个http处理模块,加在context.EndRequest += new EventHandler(this.BaseModuleRewriter_AuthorizeRequest); 事件上,由于每次访问缓存页面都要执行这个http处理模块,记录访问参数,对缓存页的访问参数数量做排序,所以我的想法是要避免对内存数据的锁,记录数据要写在一个环型队列或者两个堆栈上,用异步事件来处理。实现对内存数据的读写分离。排序结果要做序列化,保留在磁盘上。所以有必要对缓存的算法做升级。现在看来更好的做法是把换存控制写成抽象类,开放处来 根据具体的需求自由改变缓存控制类,这样才是更好的。
新加了一种url重写的方法。直接分析文件名,选用不同的处理类。具体看demo内
在配置文件上需要加上
<!-- 是否动态检测模板,和保留文件格式 -->
<add key="ExamineTemplate" value="1"/>
</appSettings>
为1 表示 每次都检测模板文件是否更新,保留输出内容的排版格式。
为0 表示 不每次都检测文件是否更新,不保留输出内容的输出格式。
这样可以速度更快 ,输出的内容更小。如果不在配置文件加上上述内容,那默认是按1来执行。如果要在页面内使用ajax,只要把ajax类注册放在逻辑层。 调用放在页面上就可以了。就象在静态页上使用ajax一样就可以。