注:由于本文原在word文档里编写,写本文章时运用了大量截图,直接复制到博客里,没有显示图片,
图片只是一些简单的运行结果截图,不影响大家学习
p.Net MVC已经到第三版了,相信大家也都熟悉了,我也不再重复相关概念性的东西了。但是大家一定要了解,Asp.Net MVC是微软的一个开源的UI层框架,是AspNet的另外一种开发模式。好废话不多说,那我们开始进入Asp.Net MVC3 的学习中来,工欲善其事,必先利其器!所以我们必须搭建好自己的开发环境才能为我们下一步的学习和开发提供更好的支持。
那你的机器的必备条件是:
1)VS2010(当然你非要使用VS2008那我也不能说什么了)
2)SqlServer 2000/2005/2008
3)Asp.Net MVC3安装包(应该是需要VS2010SP1)
下面提供一些URL链接方便大家下载学习:
1)Asp.Net MVC3的官网:http://www.asp.net/mvc/mvc3
如下图所示:
这个大家在安装过程中可能时间会非常长(>=20分钟),大家忍耐一下!
2)安装Asp.Net MVC3安装包
http://www.microsoft.com/web/gallery/install.aspx?appid=MVC3
3)Asp.Net MVC3源码开源地址:
http://aspnet.codeplex.com/releases/view/58781
如下图所示:
第二节:第一个Asp.Net MVC3项目
1)创建项目:
2)选择项目的默认视图引擎
我们选择一个Empty模板,然后选择Razor视图引擎(Asp.Net MVC3中提供的新的视图引擎)选择HTML5标记支持打上勾(这块我也不了解,呵呵,希望做过这块研究的高手跟我分享一下!)。
3)创建后的项目:
Asp.Net MVC3貌似跟之前的版本创建的项目模板没什么大的不同,文件夹也基本相似。当然我们看到文件夹内的Jquery的包更新到了1.5.1 。后面的文章中会对每个文件夹都做相关的介绍。在此就不多啰嗦了。
4)添加代码,跑起来我们的第一个Demo
首先:在Controller文件夹上右击,选择添加菜单,然后选择Controller,如下图所示:
然后弹出对话框,将Controller命名为HomeController,注意后面的Controller不要去掉,不然它就不会被识别为Controller了,最后点击添加。如下图所示:
设计器自动为我们生成代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}
下一步我们在Action上添加我们需要的视图,如下所示:
5)在前台页面添加我们自己的Html标签
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<h1>Hello! It's my first Asp.Net MVC Web!</h1>
注意:红色为我们自己添加的部分
6)最终结果:
在上一篇文章Asp.Net MVC3 简单教程(一)环境准备 中我简单介绍了Asp.Net MVC3项目的安装和第一个Asp.Net MVC3项目的基本情况。没有详细介绍项目中各个文件夹的作用,以及创建的第一个页面是怎样运行起来的?还有好多的疑问,那在这篇文章中我们将详细介绍项目中各个文件夹的作用,并真的第一个项目我们简要介绍一下Asp.Net MVC的URL驱动的是怎么回事。
第一节:Asp.Net MVC3项目介绍
让我们先看一下,一个普通的Asp.Net MVC3项目的样例,如下图所示
跟WebFrom还是有区别的,如果你已经了解Asp.Net MVC2的话,那就感觉异常熟悉了!但还是有些区别的。不管怎样我们都一一介绍一下。
很有意思的事情是即使我们创建一个空的MVC项目,VS也自动帮我们创建以上图所示的目录,这是为何呢?这是由于MVC秉承了“约定大于配置”的思想,我们在使用Asp.Net MVC3开发项目时也要注意,一定要按照它的约定办事,比如:Controller在返回Action后需要一个View进行展示(当然是调用了View()方法时),这时候Asp.Net MVC回到Views文件夹下找到Controller名字相同的文件夹下面找到具体的页面进行渲染,当然如果找不到会去Shared文件夹下去找。看下表所示的就是Asp.Net MVC3中各个文件夹的作用。
文件夹 |
作用 |
/Controllers |
存放控制器类【职责是:处理用户的请求,指挥具体的页面进行渲染交给客户端】 |
/Views |
存放各个控制器对应的视图文件,如果是Razor引擎的话那后缀是cshtml.如果使用的WebFrom的视图引擎的话,那还是Aspx后缀。 |
/Content |
主要存放照片、CSS、Flash等文件 |
/Scripts |
主要存放脚本文件【微软默认给我们提供了JQuery1.5.1的包,看来JQuery已经成为默认的工业标准了!我们没有退路了,呵呵,当然我个人也非常喜欢JQuery】 |
/Models |
主要存放ViewModel类【当然这个不是严格这样要求的,而是推荐你这么做。】 |
其他的几个比较有意思的文件:
一个是Web.Config,另外一个是Global.asax虽然我们大家都非常熟悉,但是跟之前我们WebFrom还是有很多的区别的。WebConfig文件中,配置了启用客户端脚本验证、配置了System.Web.Routing、System.Web.Mvc等组件。而Global.asax则在应用启动的时候注册了全局的Area【区域,后面会相信讲解】、全局Filter、路由等。
第二节:Asp.Net MVC的请求处理模型
在上一篇中我们也简单做了个小例子,直接添加一个Controller,然后在Action上添加一个View,直接运行,然后就在我们面前呈现了一个普通的Html页面。那我们详细解释一下这种开发方式或者说开发模型。在讲解之前我们先认识几个概念:
Controller:控制器。在Contrller文件夹添加的以Controller结尾的类就是控制器,它的每个方法就是一个Action。它的职责是从Model中获取数据,并将数据交给View,它是个指挥家的角色,它并不控制View的显示逻辑,只是将Model的数据交给View,而具体的怎样展示数据那是View的职责,所以Controller跟View是一个弱耦合的状态,而且Controller可以任意指定具体的View进行渲染。所以达到了UI层的代码和实体良好的分离。
View:视图.负责数据的展示,当然这个视图代码的编写应该是更接近纯净的Html的,而View层代码的书写又直接跟视图引擎解析的规则有关,所以Razor的语法跟webFrom视图引擎的语法截然不同。而笔者更倾向更喜欢Razor语法的简洁、方便。
Model:很多人把Model理解成领域模型,而MVC本身是一个表现模式,它是更倾向于UI层的一个框架,所以一般我们指定的Model呢在使用时一般作为ViewModel来用,但是总的MVC的思想呢,Model还是领域相关的东西吧。
经过MVC3个模块的了解分析,我们大体也知道了Asp.Net MVC的一些基本的概念。接下来我们分析一个完整的Http的处理过程。看下面一个图:
客户端发送一个Http请求,首先被我们的IIS捕获到,然后根据Url请求的格式,最终交给我们的Route组件,然后它负责解析出我们的Url具体请求的是哪个Controller下的哪个Action。然后MVC经过处理调用我们的Action执行。在Action中我们一般会从业务的Façade层取出数据,然后将传输层的数据转换成ViewModel再交给View的视图引擎渲染,最终生成Html的字节流写回客户端。
回到我们第一个项目中的情况是,请求:Http://localhost/Home/Index请求过来,由Route组件解析出Controller是Home,Action是Index,则通过工厂创建一个Controller的实例,然后调用InvokeAction方法,执行Index的方法,最终执行View()方法返回一个ViewResult实例,再调用自己的ExcuteResult方法,将数据上下文和输出流交给视图引擎,然后最终渲染成Html页面交给客户端,最终就看到了我们的第一个页面。
总结一下:
Asp.Net MVC所有的请求都归结到Action上,而且Asp.Net MVC请求--处理--响应的模型非常清晰,而且没有WebFrom那种复杂的生命周期,整个请求处理非常明晰简单,又回归到了最原始的Web开发方式,就是简单的请求处理响应!
前言
前面两篇写的比较简单,刚开始写这个系列的时候我面向的对象是刚开始接触Asp.Net MVC的朋友,所以写的尽量简单。所以写的没多少技术含量。把这些技术总结出来,然后一简单的方式让更多的人很好的接受这是我一直努力的方向。后面会有稍微复杂点的项目!让我们一起期待吧!
此文我将跟大家介绍一下Asp.Net MVC3 Filter的一些用法。你会了解和学习到全局Fileter,Action Filter等常用用法。
第一节:Filter知识储备
项目大一点总会有相关的AOP面向切面的组件,而MVC(特指:Asp.Net MVC,以下皆同)项目中呢Action在执行前或者执行后我们想做一些特殊的操作(比如身份验证,日志,异常,行为截取等),而不想让MVC开发人员去关心和写这部分重复的代码,那我们可以通过AOP截取实现,而在MVC项目中我们就可以直接使用它提供的Filter的特性帮我们解决,不用自己实现复杂的AOP了。
Asp.Net MVC提供了以下几种默认的Filter:
Filter Type |
实现接口 |
执行时间 |
Default Implementation |
Authorization filter |
IAuthorizationFilter |
在所有Filter和Action执行之前执行 |
AuthorizeAttribute |
Action filter |
IActionFilter |
分别在Action执行之前和之后执行。 |
ActionFilterAttribute |
Result filter |
IResultFilter |
分别在Action Result执行之后和之前 |
ResultFilterAttribute |
Exception filter |
IExceptionFilter |
只有在filter, 或者 action method, 或者 action result 抛出一个异常时候执行
|
HandleErrorAttribute |
大家注意一点,Asp.Net MVC提供的ActionFilterAttribute默认实现了IActionFilter和IResultFilter。而ActionFilterAttribute是一个Abstract的类型,所以不能直接使用,因为它不能实例化,所以我们想使用它必须继承一下它然后才能使用,下图所示的是ActionFilterAttribute的实现:
所以我们在实现了ActionFilterAttribute,然后就可以直接重写一下父类的方法如下:
public virtual void OnActionExecuted(ActionExecutedContext filterContext);//在Action执行之后执行
public virtual void OnActionExecuting(ActionExecutingContext filterContext); //在Action执行前执行
public virtual void OnResultExecuted(ResultExecutedContext filterContext);//在Result执行之后
public virtual void OnResultExecuting(ResultExecutingContext filterContext); //在Result执行之前
然后我们就可以直接在Action、Result执行之前之后分别做一些操作。
第二节:Action Filter实战
光说不练假把式,那现在我们就直接做一个例子来实际演示一下。
首先我们添加一个普通的类,直接上代码吧:
public class DemoActionAttributeFilter : ActionFilterAttribute
{
public string Message { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{ //在Action执行之后执行 输出到输出流中文字:After Action Excute xxx
filterContext.HttpContext.Response.Write(@"<br />After Action Excute" + "\t " + Message);
base.OnActionExecuted(filterContext);
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{ //在Action执行前执行
filterContext.HttpContext.Response.Write(@"<br />Before Action Excute" + "\t " + Message);
base.OnActionExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{ //在Result执行之后
filterContext.HttpContext.Response.Write(@"<br />After ViewResult Excute" + "\t " + Message);
base.OnResultExecuted(filterContext);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{ //在Result执行之前
filterContext.HttpContext.Response.Write(@"<br />Before ViewResult Excute" + "\t " + Message);
base.OnResultExecuting(filterContext);
}
}
写完这个代码后,我们回到Action上,打上上面的标记如下所示:
[DemoActionAttributeFilter(Message = "action")]
public ActionResult Index()
{ //Action 执行时往输出流写点代码
this.ControllerContext.HttpContext.Response.Write(@"<br />Action Excute");
return Content("Result Excut! ");
}
然后执行F5,页面上则会显示为:
最终我们看到了在Action执行之前和之后都执行了我们的重写的DemoActionAttributeFilter方法,Result执行前后也执行了我们的Filter的方法。
总的执行顺序是:
Action执行前:OnActionExecuting方法先执行→Action执行→OnActionExecuted方法执行→OnResultExecuting方法执行→返回的ActionRsult中的ExcuteResult方法执行→OnResultExecuted执行。最终显示的效果就是如上图所示。
感觉很爽吧!呵呵!
如果我们将此标签打到Controller上的话,DemoActionAttributeFilter将作用到Controller下的所有的Action。例如如下代码所示:
[DemoActionAttributeFilter(Message = "controller")]
public class HomeController : Controller
{
[DemoActionAttributeFilter(Message = "action")]
public ActionResult Index()
{
this.ControllerContext.HttpContext.Response.Write(@"<br />Action Excute");
return Content("<br/>Result Excut! ");
}
}
那就有个问题了我们再执行显示的页面会有什么情况呢?Controller上的Filter会执行吗?那标签的作用会执行两次吗?下面是最后的执行结果如下图所示:
结果说明:默认情况下Action上打了DemoActionAttributeFilter 标签后,虽然在Controller上也打上了此标签,但它只有Action上的标签起作用了。Index 执行时,Filter的方法只执行了一次,而某些情况下我们也想让Controller上的FilterAttribute也执行一次DemoActionAttributeFilter
那我们怎么才能让Controller上的[DemoActionAttributeFilter(Message = "controller")]也起作用呢?
答案是:我们只需在DemoActionAttributeFilter类的定义上打上标记[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]即可【下面类的最上面红色字体部分】,也就是让其成为可以多次执行的Action。代码如下:
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class DemoActionAttributeFilter : ActionFilterAttribute
{
public string Message { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Response.Write(@"<br />After Action Excute" + "\t " + Message);
base.OnActionExecuted(filterContext);
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write(@"<br />Before Action Excute" + "\t " + Message);
base.OnActionExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Write(@"<br />After ViewResult Excute" + "\t " + Message);
base.OnResultExecuted(filterContext);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write(@"<br />Before ViewResult Excute" + "\t " + Message);
base.OnResultExecuting(filterContext);
}
}
然后我们执行的效果如图所示:
我们看到的结果是Controller上的ActionFilter先于Action上打的标记执行。同样Result执行ExcuteResult方法之前也是先执行Controller上的Filter标记中的OnResultExcuteing方法。
最后的执行顺序是:Controller上的OnActionExecuting→Action上的OnActionExecuting→Action执行→Action上的OnActionExecuted→Controller上的OnActionExecuted
到此Action就执行完毕了,我们看到是一个入栈出栈的顺序。后面是Action返回ActionResult后执行了ExecuteResult方法,但在执行之前要执行Filter。具体顺序为:
接上面→Controller的OnResultExecuting方法→Action上的OnResultExecuting→Action返回ActionResult后执行了ExecuteResult方法→Action上的OnResultExecuted执行→Controller上的OnResultExecuted执行→结束
第三节:Gloable Filter实战
又接着一个问题也来了,我们想有些公共的方法需要每个Action都执行以下,而在所有的Controller打标记是很痛苦的。幸好Asp。Net MVC3带来了一个美好的东西,全局Filter。而怎么注册全局Filter呢?答案就在Global.asax中。让我们看以下代码,我是如何将上面我们定义的DemoActionAttributeFilter 注册到全局Filter中。上代码:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new DemoActionAttributeFilter() { Message = "Gloable" });
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
跟普通的MVC2.0中的Global.asax的区别就是红色部分的代码,我们看到代码中我将自己定义的DemoActionAttributeFilter的实例加入到GlobalFilters.Filters集合中,然后下面一句就是注册全局Filter:RegisterGlobalFilters(GlobalFilters.Filters);
这样我们所有的Action和Result执行前后都会调用我们的DemoActionAttributeFilter的重写的方法。
再次运行我们的demo看到的结果是:
我们看到的结果是全局的Action首先执行,然后才是Controller下的Filter执行,最后才是Action上的标签执行。当然这是在DemoActionAttributeFilter类的定义上打上标记[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]的前提下。不然 如果Action打上了标签跟Controller的相同则它只会执行Action上的Filter。
总结
经过这一篇文章的介绍我们大体了解了Filter的使用方法,还了解到全局Filter的用法,尤其是当相同的Filter重复作用到同一个Action上时,如果没有设置可多次执行的标签那只有Action上的Filter执行,而Controller和全局Filter都被屏蔽掉,但是设置可多次执行,那首先执行全局Filter其次是Controller再次之就是Action上的Filter了。
今天写点关于Asp.Net MVC的PipeLine。首先我们确认一点,Asp.Net WebFrom和Asp.Net MVC是在.Net平台下的两种web开发方式。其实他们都是基于Asp.Net Core的不同表现而已。看下面一张图,我们就能理解了WebForm和Asp.Net MVC的一个关系了。
那好我们了解了Asp.Net平台下的两种开发方式,相信大家对于WebForm的Pipeline都非常熟悉了,当然这也是你熟悉Asp.Net开发的必经之路。而看了很多关于Asp.Net MVC的资料很少有把整个Pipeline讲的非常清楚的。我暂时将自己浅陋的整理和理解总结如下,欢迎高手拍砖!
第一阶段:客户端请求
客户端通过浏览器、其他软件、自己编写WebClinet、模拟HttpRequest等方法来请求一个URL。当然在Asp.Net WebFrom下,所有的请求都是归结到Handler上,普通的Aspx、Ascx等都是继承自IHttpHandler接口的一些实例,所以我总结出来:WebFrom下所有的请求都是请求的Handler【不考虑Url重写】。而做Asp.Net MVC的项目呢,所有的请求是都归结到Action上,Url应该是直接请求Action。
第二阶段:IIS Web服务器
当一个请求到达IIS服务器后,Windows系统的内核模块 HTTP.SYS就能监听到此次请求,并将此次请求的URL、IP以及端口等信息解析出来并将此请求交给注册的应用来处理:也就是IIS的站点。请求此时就到达了IIS,IIS【此处仅代表IIS6.0版本】就会去检查此次请求的URL的后缀并将相应的请求交给配置的处理后缀相应的isapi。如果是.aspx或者ascx等直接交给默认设置了此处理项的AspNet_isapi.dll来处理,如果我们想处理Asp.Net MVC的请求的话,我们需要在IIS里面设置处理*.*请求交给AspNet_isapi.dll来处理,才能将一个普通的MVC请求的URL:Http://localhost/DemoController/DemoAction交给AspNet_Isapi.dll来处理。
第三阶段:Asp.Net 运行时
此时请求到AspNet_Isapi.dll后,它负责启动Asp.Net RunTime【如过启动了,直接将请求交给RunTime】。Asp.Net 运行时【HttpRuntime】此时会初始化一下HttpContext上下文,并从HttpApplicationFactory去创建一个HttpApplication对象,并将HttpContext赋值给HttpApplication,此后HttpContext的信息就会一直在管道内往下传递。
HttpApplication对象开始初始化WebConfig文件中注册的IHttpModule,请求带着请求信息【HttpContext】随着管道流过多个HttpModule【一般可以做为权限校验、行为记录、日志等等,就是在到达Handler之前我们都可以直接处理此次Http请求,甚至可以重写URL】,当然也会经过我们注册的一些自定义的IHttpModule,在.Net 4.0的machine 的config文件中默认配置了一个URLRouteModule,这个也就是我们普通的Asp.Net MVC项目中的路由DLL引用【System.Web.Routing】内部的一个实现了IHttpModule接口的实例类。请求最终流向了路由组件。
第四阶段:Routing组件
如果你用的是MVC 2+ .NET 3.5,则你会在你的web项目中发现UrlRoutingModule就配置在你的Web.Config。.NET 4却是在.Net的默认配置文件中配置的。
UrlRoutingModule做了这么几个工作:首先他会拿着你的请求到路由表中去匹配相应的路由规则。而路由表规则的定义是在HttpApplication初始化的时候由静态方法执行的,且看一个普通的Asp.Net MVC项目的Global.asax
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)//定义路由表规则
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // 路由名称
"{controller}/{action}/{id}", // 带有参数的 URL
new { controller = "Home", action = "Index", id = UrlParameter.Optional } //参数默认值
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);//注册路由表
}
}
而路由表的规则的注册是在 Application_Start() 方法内部,那此时请求在URLRouteModule内部到路由表中的所有规则进行匹配,并把匹配的Controller的信息和Action的信息以及RouteData等信息都解析处理,然后将请求进一步交给:实现了IRouteHandler【实现了IHttpHandler接口】 的一个实例,下面是IRouteHandler的源码:
namespace System.Web.Routing
{
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
}
如果你想自己来实现这个接口然后在Web.Config中配置一下,那么请求就到了你自己的自定义的RouteHandler来执行后续的请求处理操作了。如果你使用的是默认的配置,那么请求会传递到MvcRouteHandler,那么请求f附加着HttpContext就会到达Asp.Net MVC的处理中了。
第五阶段:MvcRouteHandler创建Controller
请求到此,其实跟WebForm都是一致的,而后面才出现了一些不同,此时请求才真正的进入System.Web.Mvc控制的领域内。后面所有的东西我们都可以直接通过源码来介绍了,而上面的所有的请求处理只能通过反射等方式来看或者学习,而后面的内容,我们可以幸福的直接看源码了。那就跟我走进它的管道怎么流动的吧...
接着上面讲,请求到了MvcRouteHandler类,而此类的源码如下:
namespace System.Web.Mvc
{
using System.Web.Routing;
using System.Web.SessionState;
public class MvcRouteHandler : IRouteHandler
{
private IControllerFactory _controllerFactory;
public MvcRouteHandler()
{
}
public MvcRouteHandler(IControllerFactory controllerFactory)
{
_controllerFactory = controllerFactory;
}
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}
.....
}
MvcRouteHandler的GetHttpHandler方法被URLRouteModule调用,而看上面的红色源码部分我们看到,它将请求上下文交给了MVCHandler,并返回了MVCHandler。
而我查看源码得知:MVCHandler实现了IHttpHandler,此时它的ProcessRequest方法被调用。且看MVCHandler的部分源代码:
1 public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
2 {
3 protected internal virtual void ProcessRequest(HttpContextBase httpContext)
4 {
5 SecurityUtil.ProcessInApplicationTrust(() =>
6 {
7 IController controller;
8 IControllerFactory factory;
9 ProcessRequestInit(httpContext, out controller, out factory);//初始化了ControllerFactory
10 try
11 {
12 controller.Execute(RequestContext);
13 }
14 finally
15 {
16 factory.ReleaseController(controller);
17 }
18 });
19 }
20 }
从源码中我们得知:请求交给MVCHandler后,它首先从ControllerBuilder获取到当前的实现了IControllerFactory接口的ControllerFactory【也可以自己定义相关的CustomerControllerFactory,然后在Glable中注册使用】。然后根据上下文中请求的Controller的字符串信息创建出实现了IController接口的控制器。然后调用了上面代码中红色部分,也就是controller.Execute(RequestContext);那此时请求就交给了controller。
第六阶段:Controller调用Action返回ActionResult
由于此文过长,而且时间已经到了凌晨。源码我就不贴了,简单介绍一下流程,后面再做详细赘述。
Controller的Execute方法是在基类ControllerBase中的方法,而此方法又调用ExecuteCore方法,然后此方法内部执行如下代码:
string actionName = RouteData.GetRequiredString("action");
if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
{
HandleUnknownAction(actionName);
}
首先从RouteData中获取Action的名字,然后调用ActonInvoker的InvokeAction方法,调用Action执行。Action的返回的ActionResult的ExecuteResult(controllerContext)方法被执行,那此时就出现了分叉。如果直接返回的非ViewResult的话,那就直接协会到Respose流了返回客户端了,如果是ViewResult的话,那就进入View的领域了。
第七阶段:View视图加载成Page类,并Render成Html
21 public override void ExecuteResult(ControllerContext context)
22 {
23 if (context == null)
24 {
25 throw new ArgumentNullException("context");
26 }
27 if (String.IsNullOrEmpty(ViewName))
28 {
29 ViewName = context.RouteData.GetRequiredString("action");
30 }
31 ViewEngineResult result = null;
32 if (View == null)
33 {
34 result = FindView(context);
35 View = result.View;
36 }
37 TextWriter writer = context.HttpContext.Response.Output;
38 ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
39 View.Render(viewContext, writer);
40 if (result != null)
41 {
42 result.ViewEngine.ReleaseView(context, View);
43 }
44 }
内部主要是通过ViewResult的FindView方法通过ViewEngine去加载具体的Aspx页面或者是cshtml页面生成对应的page类【针对Aspx】,然后再调用IView接口的Render方法将请求信息+ViewData的信息以等一块渲染成Html并写回到客户端。
在此阶段我们发现IViewEngine内部的实现这是到规定路径下去加载Aspx页面生成对应的ViewPage类。
IView接口的Render方法才是真正的去将Html和数据装配的到一块。自此请求结束。
总结:
客户端请求→路由器→IIS服务器内核模块HTTP.SYS→IIS→AspNet_isapi.dll→Asp.Net Runtime→Application→IHttpModule....IHttpModule→MVCRouteModule→MVCRouteHandler→MVCHandler→ControllerFactory
→Controller→ActionInvoke→Aciton→ActiongResult.ExcuteReuslt()【如果是ViewResult】→IViewEngine FindView→IView Render→Response
最后附两张关于此请求管道的两张图,以飨读者。
引子
本文将主要演示怎么将多个Asp.Net MVC项目部署到一个IIS站点中的例子,主要使用的是Asp.Net MVC提供的区域的功能。
Asp.Net MVC提供了区域的功能,可以很方便的为大型的网站划分区域。可以让我们的项目不至于太复杂而导致管理混乱,有了区域后,每个模块的页面都放入相应的区域内进行管理很方便。而随着项目的复杂,每个开发人员开发的模块呢也可能是一个完整的解决方案,而他要开发的UI项目呢只是主站点项目的一个区域,而如果把所有的UI项目放到一个UI项目,在团队开发时就不很方便了,而我们想达到的效果是:每个模块都对应一个UI项目【这里指Asp.Net MVC项目】,最后部署的时候将子项目都配置成区域,而总的项目就是一个站点。
一、项目创建
首先创建一个主Asp.Net MVC项目,然后创建一个子Asp。Net MVC项目。项目的结构如下:
注:
1、AreasDemo【子项目,作为主项目的一个Area】、MvcAppMain【主Web项目】都是普通的Asp.Net MVC3项目
2、MVCControllers是一个类库项目
3、补充:Asp.Net MVC的控制器:Controller是可以放到站点的任何DLL中的,它在搜索控制器时,会搜索站点下的所有DLL,当类符合条件:不是静态类,类名以Controller结尾,实现了Controller基类【其实最主要是IController接口】的条件时它就会被识别为控制器。所以我们可以把控制器放到任何的其他项目中,只有将此控制器所在的DLL拷贝到、主站点的Bin目录或者对应的DLL目录就可以了。当然也可以放在默认的Web项目中的Controller文件夹下。
二、添加测试的Controller和Action
在子区域Web项目AreasDemo项目中添加一个Action,然后添加一个对应的视图
在主Web项目MvcAppMain中添加一个HomeController和相应的Index.cshtml视图文件。
在MVCAppMain项目中添加一个Admin区域,做测试使用。
项目最终截图为:
我们看到,在主站点里添加了一个Admin区域后,默认创建了一个Areas文件夹,而且内部就是存放区域项目的页面。
三、在子项目中添加Areas Registration类
打开AreasDemo项目,添加一个AreasDemoRegistration类文件,其代码如下:
1 public class AreasDemoRegistration : AreaRegistration//在主站点注册区域
2 {
3 public override string AreaName
4 {
5 get { return "AreasDemo"; }
6 }
7 public override void RegisterArea(AreaRegistrationContext context)
8 {
9 context.MapRoute(
10 "AreasDemo_default",//路由名字,必须唯一
11 "AreasDemo/{controller}/{action}/{id}",//路由规则
12 new { action = "Index", id = UrlParameter.Optional }//默认值
13 );
14 }
15 }
其实就是一个普通的类,它实现了AreaRegistration基类。然后我们注册区域路由就会在Global.asax的Application_Start事件方法中去执行注册到主站点的路由表中。具体
可以参考Global.asax中红色代码部分:
16 protected void Application_Start()
17 {
18 AreaRegistration.RegisterAllAreas();//注册所有区域
19 RegisterGlobalFilters(GlobalFilters.Filters);
20 RegisterRoutes(RouteTable.Routes);
21 }
至此我们基本的测试的基础工作都做好了,下面就是到了部署阶段了。
四、部署我们的项目
首先,我们需要将子项目的引用到主项目中。然后我们发布主项目到一个磁盘文件夹。然后,将子项目AreasDemo的Views文件夹拷贝到主项目发布后的文件夹对应的Areas\AreasDemo文件夹下。其中AreasDemo是areaname,此文件夹需要我们自己手动创建。然后,观察发布后的bin目录下有没有AreasDemo.dll动态链接库【Web子项目】。
然后,我们将此文件夹发布为IIS里的一个网站。最终演示效果为:
注:这是默认主Web的inde页面
注:这是主站点里添加的Admin区域
注:这是子项目action请求返回的页面