写自己的ASP.NET MVC框架(上)-深圳网站优化推广公司

写自己的ASP.NET MVC框架(上)

44 篇文章 0 订阅
【用Asp.net写自己的服务框架】中, 发布了一个用ASP.NET写的服务框架,那个框架我目前仍在使用中。近来,由于时常也会有人问我一些关于ASP.NET MVC的话题, 因此,就想再写个自己的MVC框架出来,一方面可以留给自己使用,另外也可以谈谈MVC,尤其可以展示一下在ASP.NET下写框架的乐趣。

我之所以将写框架看成是件有乐趣的事,是因为:在写框架的过程中会接触许多的技术细节。
比如:
1. 为了支持Session需要了解管线过程以及支持Session工作的原理。
2. 在HttpHandler的映射过程中,HttpHandlerFactory的使用会让设计很灵活。
3. 反射可以让我们轻松地根据一个URL去寻找匹配的Action以及为Action准备传入参数。
4. 为了便于测试Action,如何有效的封装框架的功能(这里有许多ASP.NET的技术细节)。
5. 如何设计让框架更灵活更强大。

在开始今天的博客之前,我想有必要说说我的框架的规模:
如果说ASP.NET WebForm是个复杂框架,ASP.NET MVC是个轻量级框架的话,那么,我的MVC框架将只是个微量级的框架。
但这个微量级的框架却可以提供许多实用的功能(因为我没有引入一些与MVC无关的东西),而且完全遵守MVC的思想而设计。
由于我的框架规模实在太小,因此,有些地方的确是不够完善,但我认为在大多数情况下是够用的。

回到顶部 回到顶部 回到顶部 回到顶部

当然了,也有人会选择创建一个空的aspx去代替ashx,而且使用aspx还可以只输出一个HTML片段。

在这种原始的方式下,整个处理过程可以大致分为注释中所标注的三个阶段。 如果使用这种方式去做服务端的AJAX开发,当AJAX的数量到达一定规模后,可以发现:大量的代码是类似。我之所以称为【类似】,是因为它们却实有差别,差别在于:参数的名字不同,参数的类型不同,参数的个数不同,要调用的方法以及返回值不同。

说实话,这种机械代码我也写过。
不过,当我发现时这个现象时,我就开始想办法去解决这个问题,因为我非常不喜欢写这类重复性质的代码。
在重构过程中,也逐渐形成了我自己的AJAX服务端框架。
后来我把它写到我的第一篇博客中了: 【晒晒我的Ajax服务端框架】

在AJAX的发展过程中,微软曾经推出过ASP.NET AJAX框架,它可以在服务端生成一些JS的代理类,让客户端的JS方便地调用服务端的方法。 虽然那个框架设计地很巧妙,并且与WebForm配合地很完美,只可惜那个框架不够流行。 后来的WCF通过一些配置也可以让JS去调用,不过,喜欢的人也不多,可能还是因为配置麻烦的缘故吧。 当后来微软推出了ASP.NET MVC框架时,一些人开始惊呼:AJAX非ASP.NET MVC框架不可。 因为ASP.NET MVC框架可以很容易让JS去调用一个C#方法,从此以后,再也不用去【读参数,调用方法,写输出】这些繁琐的事情了, 而且没有WCF那么复杂的配置。 的确,他们没有解决的问题,ASP.NET MVC框架很好地解决了。

今天的博客,我将向大家介绍我的AJAX解决方案,它同样可以很好的解决上面的那些繁琐的过程。

回到顶部 回到顶部

有了MyMVC,就几乎上不需要再去访问QueryString,Form这些对象了。 你需要什么参数,只要写在方法的签名中就可以了。参数可以是简单的数据类型,也可以是自定义的数据类型,参数的个数也没有限制。

不过,有一点我要提醒您:所有的数据来源只有二个地方:QueryString和Form,框架只读取这二个地方,而且直接访问它们的索引器。 由于QueryString,Form这二个类型都是NameValueCollection,而NameValueCollection的索引器在实现上有点独特,因此请大家注意它们的返回值。 关于NameValueCollection的细节描述,可以参考我的博客【细说 Request[]与Request.Params[]】, 今天我就不再重谈这个细节话题了。

在读取参数时,万一出现key重复了怎么办?
框架还提供另一种解决方案,那就是您可以在C#的方法的签名中,声明NameValueCollection类型的变量,变量名可以从【Form,QueryString,Headers,ServerVariables】中选择。 注意:对于后二者,框架本身也是不读取的,如果需要读取,只能使用这个方法来获取。示例代码如下: 

代码中,我同时要求框架给出这四个集合,事实上,您可以根据实际情况来决定需要多少个参数。

注意:
1. 参数名称是大小写【不敏感】的。
2. 类型一定要求是NameValueCollection 。
3. 框架会优先读取QueryString,如果没有则会查看Form
4. 千万不要在Action中使用HttpContext.Current.Request.QueryString[]的方式读取来自客户端的参数。

关于参数,还有一种特殊的情况:我在博客【细说 Form (表单)】中曾提到过, 例如,我有这样二个类型,它们的结构一样: 

如果此时我有这样一个C#方法,又该如何处理呢? 

上面的示例也可以理解成:一模一样的参数类型,就是要出现多次,再或者,多个不同的自定义类型中,有些成员的名称是相同的。
此时我的框架在设计时与微软的MVC框架一样,要求在HTML中对name做特殊的设置,示例代码如下: 

此时要求:input标签中的name必须能够反映C#方法的参数名以及类型中所包含的数据成员名称。

注意:在MyMVC框架中,自定义的数据类型所包含的数据成员不要求是属性,字段(Field)也是完全受支持的。

回到顶部

在示例代码中,我使用了【cspx】这个扩展名,如果您不喜欢,也可以选择您所喜欢的扩展名,这个不是问题。

关于配置参数中的【path】属性,请参考我的上篇博客【细说 HttpHandler 的映射过程】,这里也不再重新解释。 如果没有看过的,建议还是去看一下,下面将会用到那些知识,因为它非常重要。

回到顶部

代码中,每个步骤做了什么事情,注释中有说明,不需要再重复说明。最后创建的ActionHandler的实现代码如下: 

整个入口点就是这样的。
有没人想过:为什么不直接在web.config中映射到这个ActionHandler呢?
答案在后面,请继续阅读。

回到顶部

这段代码又涉及另外二个类型,它们的实现代码如下:

internal class RequiresSessionActionHandler : ActionHandler, IRequiresSessionState
{
}

internal class ReadOnlySessionActionHandler : 
                        ActionHandler, IRequiresSessionState, IReadOnlySessionState
{
}

不要感到奇怪,这二个类型的确没有任何代码。

它们除了从ActionHandler继承而来,还实现了另外二个接口, 那二个接口我在博客【Session,有没有必要使用它?】中已有详细的解释, 不明白的朋友,可以去阅读那篇博客。

再来回答前面那个问题:为什么不直接在web.config中映射到这个ActionHandler呢?
答:如果这样配置,那么对Session的支持将只有一种模式!
在这个框架中,我采用HttpHandlerFactory就可以轻松地实现对多种Session模式的支持。
说到这里,我真的感觉上篇博客【细说 HttpHandler 的映射过程】的研究成果太有意义了, 是它给【MyMVC对Session完美的支持】提供了灵感。

老实说:我是不使用Session的。
但看到以前的博客中有些人还是坚持使用Session,所以就决定在MyMVC中支持这个功能, 毕竟支持Session不是件难事。

下面再来说说如何支持Session 。 

在上面这段代码中,我加了一个[SessionMode]的Attribute,用它可以指定Action的Session支持模式, SessionMode是个枚举值,定义如下: 

MyMVC框架支持以上三种不同的Session模式,默认是关闭的,如果需要使用,请显式指定。 [SessionMode]既可以用于Controller类型,也可以用于Action 。

注意:Session的使用将会给Action的单元测试带来麻烦。

回到顶部 【细说 ASP.NET控制HTTP缓存】曾分析过ASP.NET Page的缓存页实现原理, 其中有个小节【缓存页的服务端编程】专门分析了Page对OutputCache的实现过程,在MyMVC中,就是使用的这种方法,具体过程可以参考那篇博客。 补充一句:微软的ASP.NET MVC也是这样做的,它也是借助了Page的强大功能。

MyMVC中的代码: 

在OutputCacheAttribute类的用法中,清楚地指出适用于类型与方法,因此,这个Attribute可以用于Controller和Action 。

说明:OutputCacheAttribute与SessionModeAttribute类似,都可以用于Controller和Action,同时使用时,Action优先匹配,代码如下: 

因此,框架只要选择一个时机调用SetResponseCache()方法就可以了,至于这个调用时机出现在哪里,请继续阅读。

回到顶部

代码很简单,核心其实就是那个正则表达式,从URL中提取Controller,Action的名字全靠它。
至于正则表达式的使用,我想这是个基本功,这里就略过了。

再来看AjaxHandlerFactory的第二个调用:

// 获取内部表示的调用信息
InvokeInfo vkInfo = ReflectionHelper.GetAjaxInvokeInfo(pair);

ReflectionHelper类是一个内部使用的工具类,专门用于反射处理,AjaxAction查找过程的相关代码如下(注意代码中的注释): 

上面就是AjaxAction查找相关的4段代码:
1. 在ReflectionHelper的静态构造函数中,我加载了所有AjaxController。
2. GetAjaxController方法用于根据一个Controller的名字返回Controller的类型描述。
3. GetAjaxAction方法用于根据Controller的类型以及要调用的Action的名字返回Action的描述信息。
4. GetAjaxInvokeInfo方法用于根据从AjaxHandlerFactory得到的ControllerActionPair描述转成更具体的描述信息。

代码中,Action的查找过程采用了延迟的加载模式,保存Action描述信息的集合我采用了线程安全的Hashtable

好了,上面那段代码我想说的就这些,剩下的就只些反射的使用,这也算是个基本功,而且也不是三言二语能说清楚的。 因此,我打算继续谈其它的内容了。

回到顶部 【用Asp.net写自己的服务框架】中的顺序)。

注意:AjaxHandlerFactory的GetHandler方法是在第10步中调用的,第12步就是在准备Session(非进程内模式), 因此,必须在第12步前决定Session的使用方式。

所有的Action代码都是在ActionHandler中执行的: 

ExecuteAction的实现过程如下: 

前面我不是没有说调用SetResponseCache()的时机嘛,这个时机就是在这里:执行完Action后。
设置过OutputCache后,就是处理返回值了。

前面那段代码中,还有一句重要的调用:

// 准备要传给调用方法的参数
object[] parameters = GetActionCallParameters(context, info.Action);

这个调用的意义在注释中有解释,关于这个过程的实现方式还请继续阅读。

回到顶部

要理解这段代码还要从前面的【查找Action的过程】说起,在那个阶段,可以获取一个Action的描述,具体在框架内部表示为ActionDescription类型: 

在构造函数的第三行代码中,我就可以得到这个方法的所有参数情况。
然后,我在就可以在GetActionCallParameters方法中,循环每个参数的定义,为它们赋值。
这段代码也解释了前面所说的只支持4种NameValueCollection集合的原因。

注意了,我在获取每个参数的类型时,是使用了下面的语句:

Type paramterType = p.ParameterType.GetRealType();

实际上,ParameterType就已经反映了参数的类型,为什么不直接使用它呢?
答:因为【可空泛型】的原因。这个类型我们需要特殊的处理。
例如:如果某个参数是这样声明的: int? id 
那么,即使在QueryString中包含id这样一个参数,我也不能直接转成 int? 使用这种类型,必须得到它的【实际类型】。
GetRealType()是个扩展方法,它就专门完成这个功能: 

如果某个参数的类型是一个自定义的类型,框架会先创建实例(调用无参的构造函数),然后给它的Property, Field赋值。

注意了:自定义的类型,一定要提供一个无参的构造函数。

为自定义类型的实例填充数据成员的代码如下: 

在给自定义的数据类型实例加载数据前,需要先知道这个实例对象有哪些属性以及字段,这个过程的代码如下: 

在拿到一个类型的所有属性以及字段的描述信息后,就可以通过循环的方式,根据这些数据成员的名字去QueryString,Form读取所需的数据了。

参数准备好了,前面的调用就应该没有问题了吧?

回到顶部

要输出返回值的时候,不仅使用了IActionResult接口,我还使用下面这个调用:

context.Response.Write(result.ToString());

不要小看了ToString()的调用。
对于自定义的数据类型来说,可以用它来控制最终输出给客户端的是JSON或者是XML, 或者是您自己定义的文本序列化格式(比如:特殊分隔符拼接而成), 因此,它有足够的能力可以取代JsonResult类型,而且同样不影响Action的单元测试。
ToString()的强大原因在于它是个虚方法,可以被派生类重写。

所以,如果您只打算返回一个数据实体对象给客户端,那么既可以实现IActionResult接口,还可以重写ToString方法。

回到顶部

由于我从来不用Page输出一段HTML,因此没有准备在Ajax中使用PageResult的示例。 但是,它们的使用方法是一样,因为:PageResult和UcResult的构造函数有着一致的签名方式。

再来说说创建UcResult对象那行代码:传入二个参数,第一个参数表示用户控件的位置(View),第二个参数表示呈现用户控件所需的数据(Model)。 至于这个地方为什么要设计二个参数,请关注我的后续博客,因为它涉及到MVC的核心思想,今天的博客不打算谈这个话题。

回到顶部

这二个类型不仅同名,而且还包含了同名的方法。(事实上,方法的签名也可以完全一样。)

那么,对于这种情况,JS如何去调用它们呢?

为了回答这个问题,我特意准备了一个示例,HTML代码如下: 

客户端的JS代码如下: 

最终的调用结果如下:

注意:下方的调用结果虽然是错误的,但表示调用的方法是正确的。

让我们再来回顾一下UrlParser类中定义的那个正则表达式吧:

internal static readonly string AjaxUrlPattern
    = @"/(?<name>(\w[\./\w]*)?(?=Ajax)\w+)[/\.](?<method>\w+)\.[a-zA-Z]+";

它可以解析这些格式的URL:

/*
    可以解析以下格式的URL:(前三个表示包含命名空间的)

    /Fish.AA.AjaxTest/Add.cspx
    /Fish.BB.AjaxTest.Add.cspx
    /Fish/BB/AjaxTest/Add.cspx
    /AjaxDemo/GetMd5.cspx
    /AjaxDemo.GetMd5.cspx
*/

值得说明的是:这个正则表达式并没有限定用什么样的扩展名,而且也不限制URL中的查询字符串参数。

但是,就算它再强大,还需要在web.config中注册时,要保证匹配的URL能被传入,否则代码根本没有机会运行。

重温httpHandlers的注册:

<httpHandlers>
    <add path="*Ajax*/*.cspx,*Ajax*.*.cspx" verb="*" 
            type="MyMVC.AjaxHandlerFactory, MyMVC" validate="true"/>
</httpHandlers>

感谢微软的天才设计,让我可以用通配符的方式写正则表达式。

回到顶部 回到顶部 【用Asp.net写自己的服务框架】中的原话)

自己写框架的好处不在于能将它做得多强大,多完美,而是从写框架的过程中,可以学到很多东西。
一个框架写完了,不在乎要给多少人使用,而是自己感觉有没有进步,这才是关键。

不管你信不信,那些喜欢说【非什么什么不可】的人,通常是从来不会写框架的。

MyMVC的介绍还未结束,下篇博客将会继续,下篇博客的重点在于UI部分的支持和实现, 这也正是MVC思想存在的必要性,当然也可以反映出MVC框架的核心价值。

说到这里,我打算给下篇博客做个预告:
MyMVC框架的后半部分在设计上主要体现了MVC这三者的关系,在设计时主要遵循了Martin Fowler大叔的总结: 从模型中分离表现和从视图中分离控制器。

最终MyMVC对于UI部分支持的结果是:多个URL可以映射到一个Action,一个Action可以将结果指定给多个View来输出。 也就是说:请求与View是一种多对多的关系,而中间的Controller只是一个。 至于Model的返回,可以由Controller根据运行的上下文条件给出不同的结果,同一个Model可以交给不同的View来显示, 也可以返回不同的Model,分别交给不同的View来显示。

写博客真不容易,为了写这篇博客,我先要写MyMVC框架以及准备示例代码,再准备一些Visio图,最后是文字部分,总共花了整整二个星期。 这还不包括前面二篇做为铺垫的博客:【细说 ASP.NET控制HTTP缓存】【细说 HttpHandler 的映射过程】。 但是,每当看到自己写的博客在博客园上拥有较高的【推荐数量】时,感觉宽慰了许多。 但愿今天的博客能受欢迎。

感谢 Amy(黄敏)同学为本文所做的校对工作,她已帮我找了好多处文字上的错误。

cookie-niu CSDN认证博客专家 CSDN认证企业博客
码龄18年 暂无认证
12
原创
7万+
周排名
1万+
总排名
87万+
访问
等级
7748
积分
117
粉丝
117
获赞
68
评论
462
收藏
私信

分类专栏

HBuilder 使用教程

CSDN-Ada助手: 哇, 你的文章质量真不错,值得学习!不过这么高质量的文章, 还值得进一步提升, 以下的改进点你可以参考下: (1)使用标准目录。

  • ASP采集-ASP采集程序原理 内容忘了复制谁的了,联系立马删。

    CSDN-Ada助手: 什么是基于编译的响应系统呢?Svelte?Vue Reactivity Transform?

  • vs2008如何生成.aspx.designer.cs文件

    CathyChen258: vs2015怎么样才能打开web对应的aspx.designer.cs文件啊

  • win10修改中文用户名

    qq_41820888: 第三部注销当前账户怎么注销?

  • Python:使用pyWin32连接Access数据库

    nl771227: 请问你安装数据库引擎了吗?

  • 目录

    python
    5篇
  • java
    15篇
  • net
    44篇
  • C#
    108篇
  • c++
  • sql
    34篇
  • js
    29篇
  • cms
    18篇
  • css
    18篇
  • 服务器
    33篇
  • 采集
    6篇
  • SEO
    2篇
  • 手机网站
    13篇
  • 开发工具
    8篇
  • 微信开发
    3篇
  • asp加密解密
    11篇
  • 抓包
    5篇
  • 为什么被折叠? 到【灌水乐园】发言
    前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值

    相关内容推荐

    哈哈网站视频制作阳信网站制作多少钱以下哪个是制作网站的软件母亲节教案网站制作制作一个网站一般要多少钱潍坊网站主页制作在线制作网站的软件网页制作完后怎么变成网站网站制作过程和流程制作一个彩票网站的费用微软网站制作干花慈溪制作企业网站公司网站制作有哪些注意事项泰州展示型网站制作收费标准期末总结网站制作潍坊网站制作干花花束武安网站制作多少钱飞向未来赤峰装修网站制作独立游戏制作网站好的制作手机封面的网站法语网站制作美食童趣网站视频制作网络问卷制作网站木艺手工制作网站岳阳网站设计制作长沙主页网站制作平顶山汽车网站制作乌兰浩特星空网站制作静态网站制作毕业论文旋转动图在线制作网站网通网站制作手工一个网站如何制作如何用手机制作免费自己的网站山东济南网站制作庐江网站制作公司芒果网站制作奶茶营销加盟网站制作免费的网页制作网站课程网站制作888动漫网站制作贵港制作网站戴尔网站制作贴纸剪裁制作视频网站桓台制作网站漳州网站制作书签宁安网站制作价格绵阳网站后台制作电脑和手机网站制作的区别成都网站制作甄选乐云seo恶魔网站制作书签泰州网站制作功能地摊网站制作小玩具情网站制作壁纸招考网站视频制作盐田工厂网站建设制作关于制作网站的ppt乐视网站制作冰淇淋自己制作习题库的网站德宏网站制作过程dw制作体育网站苏州制作网站低价LG网站制作冰淇淋吴桥企业网站制作广州普通网站制作自己制作游戏公会网站告白网站制作编程制作网站模板 橱柜龙岗网站制作美食视频歧路亡羊翻译网站制作南京网页设计网站制作怎样制作简单的网站网站制作公司秀常凡云官网环保海报制作网站上海电子网站制作流程方庄网站制作常平网站建设制作哪家好网站制作软件安卓版制作新年快乐网站asp创制作网站仪表费用广州酒店网站制作樱花网站制作表情包亳州网站制作公司找哪家青白江网站制作哪家好海口网站制作小玩具简笔画xs脚本制作网站甘肃银川网站制作价格班级网站制作banner夏邑网站制作梁平网站制作公司诚信企业推荐公司网站制作请示网站制作的好想法陕西网站制作蛋糕工具好记星网站制作表情包局域网制作简单网站企业网站制作壁纸的软件前端制作网站要学什么可制作动画表情的网站威海模板网站制作惊弓之鸟课件网站制作一个视频在线制作上传网站剪辑制作视频教程网站公众号动态图片制作网站吴川网站设计制作TWITTER网站制作奶茶上海质量网站制作技术制作手机小游戏的网站德令哈网站设计制作政府公文制作网站制作网站需要哪些材料临夏微网站制作网站视频如何制作民治营销网站制作网站标志图标制作软件网站专题制作cms如何制作网站营销天津网站门户制作开远市网站制作黄岛网站制作培训福建数字网站制作保定网站制作网页余姚有哪些网站制作迪拜网站制作诸暨好的制作网站有哪些呱呱影视网站制作简历表格制作网站苍南网站建设制作安丘网站制作方案西湖网站制作智慧论文网站制作高中信息会考网站制作英文旅游网站制作设计贵阳制作网站方法相册制作有什么网站网站的制作模板下载永州网站制作壁纸全屏新郑网站制作方案壁纸网站在线制作英德废品回收网站制作制作网站根文件夹重庆网站制作招聘网站制作都有哪些制作一个网站靠什么赚钱怎么制作自己的淘客网站制作动漫视频的网站广州网站制作方法ASP网站制作壁纸软件网站制作外包相信乐云seo姜堰市制作网站哪家专业如何去制作网站仿站鄞州区网站制作公司电脑手机网站如何制作瑞安制作地方门户网站多少钱青岛制作网站的价格网站制作软件认证苏州久远网络深圳公司的网站制作苍南如何在线制作网站链接如何网站制作渭南斗门网站制作宁河推广网站制作网站制作客户寻找广发网站视频制作武汉电子商务网站制作铁岭大型网站制作网站制作app后续缴费网站制作实训报告内容沙拉醋制作PPT模板网站东城网站制作高端济南手机网站建设制作网站制作教程简笔画收藏网站制作ppt网站制作kpi姜堰营销型网站制作欢迎咨询免费网站网页制作网站网站制作后如何上传服务器合作制作网站公司如何制作餐厅网站诈骗网站制作小玩具曲靖制作网站海盐网站制作去哪里学亦庄一站式网站制作天气网站制作壁纸应用制作网站转app松江网站设计制作服务甘肃建设网站制作义乌淘宝网站制作公司德阳网站制作哪家好正规网站制作哪家比较好手机单页网站制作教程网站制作课程故事ppt上栗网站制作唐山网站制作公司费用价格广州市网站制作服务多少钱昌吉网站制作工具衢州相册制作网站哪个最好衡东网站制作公司周口无锡网站制作周大福网站制作表格莱芜家电维修网站制作安装制作作者封面的网站通化网站制作公司价格

    合作伙伴

    深圳网站优化推广公司

    龙岗网络公司
    深圳网站优化
    龙岗网站建设
    坪山网站建设
    百度标王推广
    天下网标王
    SEO优化按天计费
    SEO按天计费系统

    >