一、前言

  对于WebForm开发,请求通常是一个以.aspx结尾的url,对应一个物理文件,从代码的角度来说它其实是一个控件(Page)。而在MVC中,一个请求对应的是一个Controller里的Action。熟悉asp.net的朋友都知道,asp.net请求实际都是交给HttpHandler处理(实现了IHttpHandler的类型)。无论是.aspx,.ashx,.asmx 还是MVC里的Action,请求都会交给HttpHandler。具体是在管道事件中,会根据请求创建一个HttpHandler,并执行它的PR方法。对于aspx和ashx都很好理解,因为它们本身就实现了IHttpHandler接口,而MVC的Controller和Action都和HttpHandler没有关系,它是如何实现的呢?接下来我们就看一个请求是如何进入mvc框架内部的。

二、例子

  WebForm和MVC都是建立在asp.net平台上的,Webform出现得比较早,那么MVC是如何做到在不影响底层框架,实现扩展的呢?这主要得益于asp.net的路由机制。路由机制并不属于MVC,WebForm也可以使用它。它的目的是让一个请求与物理文件分离,原理是通过映射关系,将请求映射到指定的HttpHandler。例如我们也可以将一个/Admin/User.aspx?name=张三 的请求映射成可读性更好的/Admin/张三。下面是两种url的注册方式:

        public static void RegisterRoutes(RouteCollection routes)
{
//MVC
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
); //WebForm
routes.MapPageRoute(
routeName: "WebForm",
routeUrl: "Admin/{user}",
physicalFile: "~/Admin/User.aspx"
);
}

  RouteCollection是一个Route集合,Route封装了名称、url模式、约束条件、默认值等路由相关信息。其中,MapPageRoute是RouteCollection定义的方法,而MapRoute是MVC扩展出来的(扩展方法的好处就是可以在不修改原有代码的情况下添加所需的功能)。它们的目的都是一样的,创建一个Route对象,添加到集合当中;我们也可以new 一个Route对象,然后调用RouteCollection.Add,效果是一样的。下面我们主要关注MVC的实现过程,WebForm其实也是类似的。

三、分析源码

  接下来我们看MVC是如何利用路由机制实现扩展的。路由机制是通过一个UrlRoutingModule完成的,它是一个实现了IHttpModule的类,路由模块已经默认帮我们注册好了。HttpModule通过注册HttpApplication事件参与到管道处理请求中,具体是订阅HttpApplication某个阶段的事件。路由机制就是利用这个原理,UrlRoutingModule订阅了PostResolveRequestCache 事件,实现url的映射。为什么是该事件呢?因为该事件的下一步就要完成请求和物理文件的映射,所以必须要此之前进行拦截。核心代码如下:

    public class UrlRoutingModule : IHttpModule {
public RouteCollection RouteCollection {
get {
if (_routeCollection == null) {
//全局的RouteCollection集合
_routeCollection = RouteTable.Routes;
}
return _routeCollection;
}
set {
_routeCollection = value;
}
} protected virtual void Init(HttpApplication application) {
//注册PostResolveRequestCache事件
application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
} private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) {
//创建上下文
HttpApplication app = (HttpApplication)sender;
HttpContextBase context = new HttpContextWrapper(app.Context);
PostResolveRequestCache(context);
} public virtual void PostResolveRequestCache(HttpContextBase context) {
//1.获取RouteData
RouteData routeData = RouteCollection.GetRouteData(context);
if (routeData == null) {
return;
}
//2.获取IRouteHandler
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null) { } //RequestContext保证了HttpContext和RouteData,在后续使用
RequestContext requestContext = new RequestContext(context, routeData); context.Request.RequestContext = requestContext; //3.获取IHttpHandler
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); //重新映射到处理程序
context.RemapHandler(httpHandler);
}
}  

  我们关注主要方法PostResolveRequestCache,这里有三个关键步骤。

  步骤一. 获取RouteData

  RouteData是对Route的包装,在后续的处理中使用。它的获取是通过RouteCollection获得的,这个和上面注册用到的RouteTable.Routes是同一个集合对象。调用RouteCollection的GetRouteData会遍历它的每一个项,也就是Route对象,然后调用Route对象的GetRouteData方法(MVC内部很多集合都用到了这种设计)。如下代码:

        public RouteData GetRouteData(HttpContextBase httpContext) {
using (GetReadLock()) {
foreach (RouteBase route in this) {
RouteData routeData = route.GetRouteData(httpContext);
if (routeData != null) {
return routeData;
}
}
}
return null;
}

  Route对象的GetRouteData方法如下:

        public override RouteData GetRouteData(HttpContextBase httpContext) {
string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; //结合默认值,匹配url
RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults); if (values == null) {
return null;
} //包装成RouteData,这里为什么不放在if后面呢?
RouteData routeData = new RouteData(this, RouteHandler); //匹配约束
if (!ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) {
return null;
} //RouteData的Values和DataTokens都来自于Route
foreach (var value in values) {
routeData.Values.Add(value.Key, value.Value);
}
if (DataTokens != null) {
foreach (var prop in DataTokens) {
routeData.DataTokens[prop.Key] = prop.Value;
}
} return routeData;
}

  可以看到,Route对象的GetRouteData方法会匹配url模式,和检查约束条件,如何不符合会返回null。如果匹配,则new一个RouteData。

  步骤二、获取IRouteHandler接口对象

  上面创建RouteData,参数分别是当前Route对象和它的RouteHandler属性。RouteHandler是一个IRouteHandler,这是一个重要接口,它的定义如下:

    public interface IRouteHandler {
IHttpHandler GetHttpHandler(RequestContext requestContext);
}

  很明显,它是用于获取IHttpHandler的。那么Route对象的RouteHandler属性又是在哪里初始化的呢?我们回到开始的注册方法,routes.MapRoute,这个方法根据传递的参数创建一个Route对象,该方法的实现如下:

        public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
//创建一个Route对象,它的IRouteHandler为MvcRouteHandler
Route route = new Route(url, new MvcRouteHandler())
{
Defaults = CreateRouteValueDictionary(defaults),
Constraints = CreateRouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
}; if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens["Namespaces"] = namespaces;
} //将Route注册到RouteCollection中
routes.Add(name, route); return route;
}

  在创建Route时,除了传递url模式外,还默认帮我们传递了一个MvcRouteHandler,它实现了IRouteHandler接口。
  步骤三、获取IHttpHandler接口对象

  有了MvcRouteHandler,就可以调用它的GetHttpHandler方法获取IHttpHandler了,该方法实现如下:

        protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
//设置session状态
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext)); //返回一个实现了IHttpHandler的MvcHandler
return new MvcHandler(requestContext);
}

  可以看到,它返回了一个MvcHandler,MvcHandler就实现了IHttpHandler接口。所以开头说的,请求本质都是交给HttpHandler的,其实MVC也是这样的,请求交给了MvcHandler处理。我们可以看MvcHandler定义和主要方法:

    public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
{
protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
{
IController controller;
IControllerFactory factory; //这个方法里会激活Controller对象
ProcessRequestInit(httpContext, out controller, out factory); IAsyncController asyncController = controller as IAsyncController;
if (asyncController != null)
{
// asynchronous controller
BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
{
try
{
//调用Controller的BeginExecute方法
return asyncController.BeginExecute(RequestContext, asyncCallback, asyncState);
}
catch
{
factory.ReleaseController(asyncController);
throw;
}
}; EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
{
try
{
asyncController.EndExecute(asyncResult);
}
finally
{
factory.ReleaseController(asyncController);
}
}; SynchronizationContext syncContext = SynchronizationContextUtil.GetSynchronizationContext();
AsyncCallback newCallback = AsyncUtil.WrapCallbackForSynchronizedExecution(callback, syncContext);
return AsyncResultWrapper.Begin(newCallback, state, beginDelegate, endDelegate, _processRequestTag);
}
else
{
// synchronous controller
Action action = delegate
{
try
{
controller.Execute(RequestContext);
}
finally
{
factory.ReleaseController(controller);
}
}; return AsyncResultWrapper.BeginSynchronous(callback, state, action, _processRequestTag);
}
}
}

  可以看到,MvcHandler的任务就是激活Controller,并执行它的Execute方法。这个过程和Webform里的页面处理是很相似的,.aspx请求到来,会根据虚拟路径找到实现IHttpHandler的Page(类似于路由机制根据url模式找到MvcHandler),然后进入Page的页面周期(类似于Mvc的激活Controller,然后执行Action过程)。

四、总结

接下来,简单总结一下请求进入到MVC框架的过程:

1.添加路由对象Route到全局的RouteCollection,Route的IRouteHandler初始化为MvcRouteHandler。

2. UrlRoutingModule注册 HttpApplication PostResolveRequestCache事件,实现请求拦截。

3. 请求到来, 在处理事件中遍历RouteCollection,调用每一个Route对象的GetRouteData获取RouteData包装对象。

4. 调用MvcRouteHandler的GetHttpHandler获取MvcHandler。

5. 调用HttpContext的RemapHandler将请求映射到MvcHandler处理程序。

6. 执行MvcHandler的PR方法,激活Controller,执行Action。

请求如何进入ASP.NET MVC框架的更多相关文章

  1. 在ASP.NET MVC 框架中调用 html文件及解析get请求中的参数值

    在ASP.NET MVC 框架中调用 html文件: public ActionResult Index() { using (StreamReader sr = new StreamReader(P ...

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

    http://www.cnblogs.com/fish-li/archive/2012/02/12/2348395.html 阅读目录 开始 ASP.NET程序的几种开发方式 介绍我的MVC框架 我的 ...

  3. 学习“迷你ASP.NET MVC框架”后的小结

    看蒋老师MVC的书第二个大收获可以是算是看了这个迷你ASP.NET MVC框架了,虽然它远不如真正ASP.NET MVC(下文简称“MVC”)那么复杂庞大,但在迷你版中绕来绕去也够呛的.这部分我看了几 ...

  4. BrnShop开源网上商城第二讲:ASP.NET MVC框架

    在团队设计BrnShop的web项目之初,我们碰到了两个问题,第一个是数据的复用和传递,第二个是大mvc框架和小mvc框架的选择.下面我依次来说明下. 首先是数据的复用和传递:对于BrnShop的每一 ...

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

    上篇博客[写自己的ASP.NET MVC框架(上)] 我给大家介绍我的MVC框架对于Ajax的支持与实现原理.今天的博客将介绍我的MVC框架对UI部分的支持. 注意:由于这篇博客是基于前篇博客的,因此 ...

  6. 如何用asp.net MVC框架、highChart库从sql server数据库获取数据动态生成柱状图

    如何用asp.net MVC框架.highChart库从sql server数据库获取数据动态生成柱状图?效果大概是这样的,如图: 请问大侠这个这么实现呢?

  7. ASP.NET MVC框架开发系列课程 (webcast视频下载)

    课程讲师: 赵劼 MSDN特邀讲师 赵劼(网名“老赵”.英文名“Jeffrey Zhao”,技术博客为http://jeffreyzhao.cnblogs.com),微软最有价值专家(ASP.NET ...

  8. 【转】ASP.NET MVC框架下使用MVVM模式-KnockOutJS+JQ模板例子

    KnockOutJS学习系列----(一) 好几个月没去写博客了,最近也是因为项目紧张,不过这个不是借口,J. 很多时候可能是因为事情一多,然后没法静下来心来去写点东西,学点东西. 也很抱歉,突然看到 ...

  9. 【工作笔记二】ASP.NET MVC框架下使用MVVM模式

    ASP.NET MVC框架下使用MVVM模式 原文:http://www.cnblogs.com/n-pei/archive/2011/07/21/2113022.html 对于asp.net mvc ...

随机推荐

  1. 8.JAVA之GUI编程键盘码查询器

    程序使用说明: 1.本程序由于是java代码编写,所以运行需安装jdk并配置好环境变量. 2. 复制java代码到记事本内,另存为Keyboard_events.java: 3.复制批处理代码到记事本 ...

  2. Android指纹识别深入浅出分析到实战(6.0以下系统适配方案)

    指纹识别这个名词听起来并不陌生,但是实际开发过程中用得并不多.Google从Android6.0(api23)开始才提供标准指纹识别支持,并对外提供指纹识别相关的接口.本文除了能适配6.0及以上系统, ...

  3. ASP.NET Core 中文文档 第二章 指南(8) 使用 dotnet watch 开发 ASP.NET Core 应用程序

    原文:Developing ASP.NET Core applications using dotnet watch 作者:Victor Hurdugaci 翻译:谢炀(Kiler) 校对:刘怡(Al ...

  4. SSM项目搭建(提供源码)

    1创建web动态项目,项目结构截图 2.配置日志文件 #\u5B9A\u4E49LOG\u8F93\u51FA\u7EA7\u522B log4j.rootLogger=INFO,Console,Fi ...

  5. iOS 相机

    本章节主要为之前项目 JXHomepwner 添加照片功能(项目地址).具体任务就是显示一个 UIImagePickerController 对象,使用户能够为 JXItem 对象拍照并保存.拍摄的照 ...

  6. 模型浏览器【Model Browser】【EF基础系列6】

    We have created our first Entity Data Model for School database in the previous section. The visual ...

  7. php调用web service接口(.net开发的接口)

    实例代码1: try { $this->soapClientObj = new SoapClient(self::URL . '?wsdl', array('connection_timeout ...

  8. 最大半连通子图 bzoj 1093

    最大半连通子图 (1.5s 128MB) semi [问题描述] 一个有向图G = (V,E)称为半连通的(Semi-Connected),如果满足:∀ u, v ∈V,满足u->v 或 v - ...

  9. Idea SpringMVC+Spring+MyBatis+Maven调整【转】

    Idea SpringMVC+Spring+MyBatis+Maven整合   创建项目 File-New Project 选中左侧的Maven,选中右侧上方的Create from archetyp ...

  10. Web报表工具FineReport的JS开发之字符串

    在报表开发过程中,有些需求可能无法通过现有的功能来实现,需要开发人员二次开发,以FineReport为例,可以使用网页脚本.API接口等进行深入的开发与控制. 考虑到JS脚本开发的使用较多,这里先先简 ...