深入浅出ASP.NET MVC5系列之一
前言
为避免看官乏味,本系列博客限定在较新的.Net framework 4.5.1,Asp.net MVC5,IIS 7.X集成模式。
对于微软应用层的技术。我向来不舍得花太多时间学习。但又由于公司现有的开发建立于Asp.net MVC5上,实在是有必要深入理解一下ASP.NET MVC5,故安排了约2-3周的时间摸摸脉,这个系列会是一边学习一边总结的产物。
从ASP.NET处理流程说起
如果自己去写一个Web服务器,最简单的方法就是:
1 监听请求-->2 获取请求信息-->3 处理请求-->4 发送响应
Asp.net大体也是做的,它干了这么点事:
1 HTTP.SYS 位于内核模式,监听请求。
2 一旦监听到请求,转给W3SRV。
3 W3SRV将请求转给WAS。
4 WAS创建并管理应用程序池进程W3WP.EXE。
5 W3WP.EXE创建运行时,处理请求。
6 发送响应。
拓展:
1 为什么需要WAS?
WAS在IIS7之前是没有的,之所以抽象出这一层,是因为WAS不再参与具体的应用层协议的解析。这么讲:IIS支持Http协议,同时也支持WCF寄宿,咋干的?我们知道WCF的协议和HTTP协议当然是不一样的,但端口只有一个,肿么办?计算机世界里有这么一句话:计算机世界的绝大部分问题都可以通过分层的方法来解决。在协议适配和数据处理之间增加一个虚拟层即可。具体的,让每一个协议都有它自己的协议解析模块,将解析出来的数据都转给WAS,最后让WAS包装成WorkerRequest封送入ASP.NET的请求管道。这里,与IIS6中不同,W3SRV不再和ASP.NET直接接洽,它只负责接受HTTP.SYS监听的HTTP协议的请求,解析成请求数据,转给WAS。那么WCF请求呢?
好的,了解得多一点的同学就知道windows上有这么个服务:NetTcpPortSharing,即端口共享服务。这么一说,好像一个端口可以被多应用程序监听一样。妈蛋,说好的世界观呢?多个进程可以共享一个端口,尼玛编程给我看看?
对Socket熟悉的同学可以自己编程试试。此处略去一千字。
好吧,先引入原文:
Internet Information Services (IIS) has a listener to share a port between multiple HTTP applications. IIS listens on the port directly and forwards messages to the appropriate application based on information inside the message stream. This allows multiple HTTP applications to use the same port number without having to compete to reserve the port for receiving messages.
这么回事。IIS有一个共享端口,这个端口在接收到基于HTTP协议请求之后,会将消息再转一份给其它同样需要处理这个请求的HTTP应用程序。我们知道WCF是有HTTPBinding嘛,那么接下来,画张图来看看:
具体细节可能不会是这样,这是我大概的一个猜测而已,但是思路定当如此。
ASP.NET处理管道
如果应用程序是第一次被访问,那么IIS在加载CLR之后,会创建应用程序域。一个特殊的运行时IsapiRuntime被加载,同时请求报文被封装为IsApiWorkerRequest.看过上篇的同学,应该知道怎么弄源码了。这里简单将源代码列出来。
public sealed class ISAPIRuntime
{
public int ProcessRequest(IntPtr ecb, int iWRType) {
IntPtr pHttpCompletion = IntPtr.Zero;
if (iWRType == WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) {
pHttpCompletion = ecb;
ecb = UnsafeNativeMethods.GetEcb(pHttpCompletion);
}
ISAPIWorkerRequest wr = null;
try {
bool useOOP = (iWRType == WORKER_REQUEST_TYPE_OOP);
wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
wr.Initialize(); // check if app path matches (need to restart app domain?)
String wrPath = wr.GetAppPathTranslated();
String adPath = HttpRuntime.AppDomainAppPathInternal; if (adPath == null ||
StringUtil.EqualsIgnoreCase(wrPath, adPath)) { HttpRuntime.ProcessRequestNoDemand(wr);
return ;
}
else {
// need to restart app domain
HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged,
SR.GetString(SR.Hosting_Phys_Path_Changed,
adPath,
wrPath));
return ;
}
}
catch(Exception e) {
try {
WebBaseEvent.RaiseRuntimeError(e, this);
} catch {} // Have we called HSE_REQ_DONE_WITH_SESSION? If so, don't re-throw.
if (wr != null && wr.Ecb == IntPtr.Zero) {
if (pHttpCompletion != IntPtr.Zero) {
UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion);
}
// if this is a thread abort exception, cancel the abort
if (e is ThreadAbortException) {
Thread.ResetAbort();
}
// IMPORTANT: if this thread is being aborted because of an AppDomain.Unload,
// the CLR will still throw an AppDomainUnloadedException. The native caller
// must special case COR_E_APPDOMAINUNLOADED(0x80131014) and not
// call HSE_REQ_DONE_WITH_SESSION more than once.
return ;
} // re-throw if we have not called HSE_REQ_DONE_WITH_SESSION
throw;
}
}
}
当请求进入HttpRuntime的时候,我们所ASP.net的处理流程正式开始。接下来便是大家熟知的了:
HttpRuntime根据httpWorkerRequest创建HttpContext,接下来利用HttpApplicationFactory创建HttpApplication对象,接下来哒哒哒哒几百行代码,二十个左右的事件处理完,Asp.net的请求就结束了。这二十个事件干了以下事:
1 权限验证
2 缓存处理
3 Map Handler
4 获取状态上下文
5 处理请求
6 保存状态上下文
7 更新缓存
8 记录日志
9 发送响应。
说好的MVC呢 怎么光Asp.net?
千呼万呼始出来,犹抱琵琶半遮面。
Asp.net中诸多的对象中,有这么两个对象尤为重要,一是Httmodule,一个是Httphandler。
Httpmodule用于订阅HttpApplication的管道事件,这赋予了Asp.net巨大的灵活性。HttpHandler用于处理请求,发送响应。当然Httpmodule也是可以处理请求的。这个区别和联系,就不在这里发散了,有兴趣的朋友可以去看选择HttpHandler还是HttpModule。
让我们来看看ASP.NET MVC是怎么玩的。
1 定义了一个UrlRoutingModule模块,订阅了上面20个事件中的的第2类事件,在这个事件中,使用 httpContext.RemapHandler 方法,将Handler定位到MvcHandler,让MvcHandler来处理请求。
2 MvcHandler根据路由表(这里等会发散),譬如
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Default", action = "Index", id = UrlParameter.Optional }
);
如果我们的URL是http://localhost,那么它变将请求映射为http://localhost/Default/Index。即DefaultController中的Index方法。接下来,用反射,处理请求。
完毕。
你可能不知道的路由表
在 routes.MapRoute方法中,我们看到有以下重载:
public static Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
前三个参数大家都很熟了,那么第四个和第五个呢?
在这个.NET开源的时代,一切都不是问题,进去喵喵:
public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens) {
if (routeUrl == null) {
throw new ArgumentNullException("routeUrl");
}
Route route = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess));
Add(routeName, route);
return route;
}
我艹,这个源码为毛是从Object变为了RouteValueDictionnary呢,先不管了,看这生成了一个Route对象。继续瞄:
public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) {
Url = url;
Defaults = defaults;
Constraints = constraints;
DataTokens = dataTokens;
RouteHandler = routeHandler;
}
将其赋值给了内部的Constraints,接下来看它在哪儿用的:
private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection) {
if (Constraints != null) {
foreach (var constraintsItem in Constraints) {
if (!ProcessConstraint(httpContext, constraintsItem.Value, constraintsItem.Key, values, routeDirection)) {
return false;
}
}
}
return true;
}
ProcessConstraint方法又是什么呢?
protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
IRouteConstraint customConstraint = constraint as IRouteConstraint;
if (customConstraint != null) {
return customConstraint.Match(httpContext, this, parameterName, values, routeDirection);
} // If there was no custom constraint, then treat the constraint as a string which represents a Regex.
string constraintsRule = constraint as string;
if (constraintsRule == null) {
throw new InvalidOperationException(String.Format(
CultureInfo.CurrentUICulture,
SR.GetString(SR.Route_ValidationMustBeStringOrCustomConstraint),
parameterName,
Url));
} object parameterValue;
values.TryGetValue(parameterName, out parameterValue);
string parameterValueString = Convert.ToString(parameterValue, CultureInfo.InvariantCulture);
string constraintsRegEx = "^(" + constraintsRule + ")$";
return Regex.IsMatch(parameterValueString, constraintsRegEx,
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
写到这儿我觉得不能讲太复杂了,简单的说,这里就是一个对URL做验证的过程,简单的说,你符合模版的同时,还得符合我设定的规则:
routes.MapRoute(
"Product",
"Product/{productId}",
new {controller="Product", action="Details"},
new {productId = @"\d+" }
);
原来的RouteValueDictionnary对象其实如下:
public class RouteValueDictionary : IDictionary<string, object>
是一个对 IDictionary<string, object>接口的实现。看了一下,我发现我微软还没有把最新的源码放上去,我现在看到的源码还不是最新的:
[TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
public class RouteValueDictionary : IDictionary<string, object>
而最新的是4.0.好吧,RouteValueDictionnary对象的作用搞明白了,那么将RouteValueDictionnary换成Object是为么干嘛?其实微软在这儿将好好的一个RouteValueDictionnary换成了一个匿名对象,如上的
new {productId = @"\d+" }
如果是原来的,就必须写成字典形式:
Key :ProductId,
Value:@"\d+"。
最后运用反射将属性从匿名对象中取出来,既然是反射,第五个参数命名空间,就有作用了。
一开始我只是对这两个参数感觉很奇怪而已,殊不知一下就翻出这么多东西。罗里吧嗦了这么多,其实就是想说,在.NET开源的这个时代,大家不应该只停留在眼上,多多看看源代码,提升.NET社区的整体水准,才是.NET社区的真正福音。
打造更强的.NET电商
找钢网武汉研发中心招聘中高级.NET工程师。扎根于钢铁行业互联网电商,已完成N轮融资,估值超十亿美金。十三薪,福利丰厚,全员持股。
这可能会是你遇到的,最好的机会。
有意向的朋友可以邮箱联系我:582105721@qq.com。最好能附上您的简历。我会第一时间转给HR。
深入浅出ASP.NET MVC5系列之一的更多相关文章
- [Asp.net MVC]Asp.net MVC5系列——在模型中添加验证规则
目录 概述 在模型中添加验证规则 自定义验证规则 伙伴类的使用 总结 系列文章 [Asp.net MVC]Asp.net MVC5系列——第一个项目 [Asp.net MVC]Asp.net MVC5 ...
- [Asp.net MVC]Asp.net MVC5系列——添加视图
目录 系列文章 概述 添加视图 总结 系列文章 [Asp.net MVC]Asp.net MVC5系列——第一个项目 概述 在这一部分我们添加一个新的控制器HelloWorldController类, ...
- [Asp.net MVC]Asp.net MVC5系列——添加模型
目录 概述 添加模型 总结 系列文章 [Asp.net MVC]Asp.net MVC5系列——第一个项目 [Asp.net MVC]Asp.net MVC5系列——添加视图 概述 在本节中我们将追加 ...
- [Asp.net MVC]Asp.net MVC5系列——从控制器访问模型中的数据
目录 概述 从控制器访问模型中的数据 强类型模型与@model关键字 总结 系列文章 [Asp.net MVC]Asp.net MVC5系列——第一个项目 [Asp.net MVC]Asp.net M ...
- [Asp.net MVC]Asp.net MVC5系列——添加数据
目录 概述 显示添加数据时所用表单 处理HTTP-POST 总结 系列文章 [Asp.net MVC]Asp.net MVC5系列——第一个项目 [Asp.net MVC]Asp.net MVC5系列 ...
- 五、 创建连接串连接本地数据库(ASP.NET MVC5 系列)
1. 创建连接串连接本地SQLServer数据库 上节讲到MovieDBContext类,这个类的作用是连接数据库并将Movie对象迁移到数据库记录中.不过你会问一个问题:如何知道这个对象将连接哪个数 ...
- 四、 添加模型Model(ASP.NET MVC5 系列)
在这一章节中我们将添加一些classes类来管理数据库中的movies.这些classes类就是ASP.NET MVC应用程序中的"model". 我们将用.NET框架中的数据访问 ...
- 三、 添加视图View(ASP.NET MVC5 系列)
在这一章节我们可以修改HelloWorldController类,通过使用视图模板来封装处理产生给客户端的HTML响应. 我们将使用Razor View engine来创建视图文件.基于Razor的视 ...
- 二、 添加控制器Controller(ASP.NET MVC5 系列)
MVC是Model-View-Controller的简写.MVC是一种开发良好架构,可测试,易维护应用程序的设计模式.据于MVC的应用程序应该包含: Models: 是呈现应用程序数据和使用验证逻辑给 ...
随机推荐
- 【Unity】改变向量的方向而不改变其大小
最近在做一个打砖块游戏时遇到一个小问题,就是小球有可能会在左右两个边界之间做循环往返运动而导致游戏无法继续进行下去,于是我打算让小球在垂直撞向边界时改变一下方向,但是速度不变,尝试了一些方法但是没有达 ...
- apache指定的网络名不再可用
如果Apache的error.log还是出现大量的:Sat Dec 24 17:21:28 2006] [warn] (OS 64)指定的网络名不再可 用. : winnt_accept: Async ...
- delphi 相对路径
..代表上级目录 .代表当前目录 \代表目录分隔 ..\..\表上上一级目录
- 网络错误定位案例 ICMP host *** unreachable - admin prohibited
1. 环境 一台物理服务器 9.115.251.86,上面创建两个虚机,每个虚机两个网卡: vm1:eth0 - 9.*.*.232 eth1:10.0.0.14 vm2: eth0 - 9.8.*. ...
- Google Cloud Platform
一个离我们很遥远,很遥远的公司.作为全球三大公有云厂商之一,在国内根本听不到他的声音.其实吧,听到了也没用,因为在国内没法用!AWS还在纠结的落地过程中挣扎,GCP基本上就当不存在吧. 抛开这些乌烟瘴 ...
- 遇到一位ITer,一位出租车司机,必看。
百木-ITer职业交流 群-北京 :141588103 今天去用户那,一个政府的事业单位. 遇到一位ITer,B同学,一个行业的,和他们公司既是合作关系,又是竞争关系.最近我们接了该单位的 ...
- 【2016-10-10】【坚持学习】【Day1】【观察者模式】
今天学习了观察者模式 定义: 一个实体变化会影响其他实体变化 例子: 红绿灯与汽车 红绿灯是观察目标 汽车是实际观察者 灯的变化会影响车是停止还是前进. 例子: 游戏中,一个战队由若干队员组成,当其中 ...
- save(),saveorupdate()还有marqe()
所有这三个方法,也就是save().saveOrUpdate()和persist()都是用于将对象保存到数据库中的方法,但其中有些细微的差别.例如,save()只能INSERT记录,但是saveOrU ...
- String的高级用法(String.Format)
string.Format C#的String.Format的一般地我们可以直接使用string.format()或int.ToString()和float.ToString() 下面是一些Strin ...
- Windows 运行时组件
Windows 运行时组件是自包含对象,可将其实例化,并可采用任一语言使用它,包括 C#.Visual Basic.JavaScript 和 C++. 你可以使用 Visual Studio 和 C# ...