Fork me on GitHub

ASP.NET MVC Preview生命周期分析

做ASP.NET WebForm开发都知道,ASP.NET有复杂的生命周期,学习ASP.NET MVC就要深入理解它的生命周期。今天从CodePlex上下载了ASP.NET Preview 2 的源代码,还有两个程序集Routing与Abstractions并未发布,不过这两个程序集的类并不多,可以用NET反编译工具 Reflector解开来看看,可惜这两个程序集用的是VS2008使用.net 3.5开发的,用了c# 3.0的很多特性,Reflector反编译不完全。

ASP.NET MVC通过HttpModule(UrlRoutingModule)开始他的执行流程

<httpModules>
     
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing" />
</httpModules> 

代码如下:

namespace System.Web.Routing
{
    
using System;
    
using System.Globalization;
    
using System.Runtime.CompilerServices;
    
using System.Security.Permissions;
    
using System.Web;
    
using System.Web.Resources; 

    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level
=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)]
    
public class UrlRoutingModule : IHttpModule
    
{
        
private static readonly object _requestDataKey = new object();
        
private System.Web.Routing.RouteCollection _routeCollection; 

        
protected virtual void Dispose()
        
{
        }
 

        
protected virtual void Init(HttpApplication application)
        
{
            application.PostResolveRequestCache 
+= new EventHandler(this.OnApplicationPostResolveRequestCache);
            application.PostMapRequestHandler 
+= new EventHandler(this.OnApplicationPostMapRequestHandler);
        }
 

        
private void OnApplicationPostMapRequestHandler(object sender, EventArgs e)
        
{
            HttpContextBase context 
= new HttpContextWrapper2(((HttpApplication) sender).Context);
            
this.PostMapRequestHandler(context);
        }
 

        
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
        
{
            HttpContextBase context 
= new HttpContextWrapper2(((HttpApplication) sender).Context);
            
this.PostResolveRequestCache(context);
        }
 

        
public virtual void PostMapRequestHandler(HttpContextBase context)
        
{
            RequestData data 
= (RequestData) context.Items[_requestDataKey];
            
if (data != null)
            
{
                context.RewritePath(data.OriginalPath);
                context.Handler 
= data.HttpHandler;
            }

        }
 

        
public virtual void PostResolveRequestCache(HttpContextBase context)
        
{
            RouteData routeData 
= this.RouteCollection.GetRouteData(context);
            
if (routeData != null)
            
{
                IRouteHandler routeHandler 
= routeData.RouteHandler;
                
if (routeHandler == null)
                
{
                    
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0]));
                }

                RequestContext requestContext 
= new RequestContext(context, routeData);
                IHttpHandler httpHandler 
= routeHandler.GetHttpHandler(requestContext);
                
if (httpHandler == null)
                
{
                    
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() }));
                }

                RequestData data2 
= new RequestData();
                data2.OriginalPath 
= context.Request.Path;
                data2.HttpHandler 
= httpHandler;
                context.Items[_requestDataKey] 
= data2;
                context.RewritePath(
"~/UrlRouting.axd");
            }

        }
 

        
void IHttpModule.Dispose()
        
{
            
this.Dispose();
        }
 

        
void IHttpModule.Init(HttpApplication application)
        
{
            
this.Init(application);
        }
 

        
public System.Web.Routing.RouteCollection RouteCollection
        
{
            
get
            
{
                
if (this._routeCollection == null)
                
{
                    
this._routeCollection = RouteTable.Routes;
                }

                
return this._routeCollection;
            }

            
set
            
{
                
this._routeCollection = value;
            }

        }
 

 

}


里面还定义了一个RequestData,主要就是当前处理的HttpHandler和URL原始路径。来看看ASP.NET 的HttpApplication 管线会依次处理下面的请求:

  1. 对请求进行验证,将检查浏览器发送的信息,并确定其是否包含潜在恶意标记。

  2. 如果已在 Web.config 文件的 UrlMappingsSection 节中配置了任何 URL,则执行 URL 映射。

  3. 引发 BeginRequest 事件。

  4. 引发 AuthenticateRequest 事件。

  5. 引发 PostAuthenticateRequest 事件。

  6. 引发 AuthorizeRequest 事件。

  7. 引发 PostAuthorizeRequest 事件。

  8. 引发 ResolveRequestCache 事件。

  9. 引发 PostResolveRequestCache 事件。

  10. 根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理。如果该请求针对从 Page 类派生的对象(页),并且需要对该页进行编译,则 ASP.NET 会在创建该页的实例之前对其进行编译。

  11. 引发 PostMapRequestHandler 事件。

  12. 引发 AcquireRequestState 事件。

  13. 引发 PostAcquireRequestState 事件。

  14. 引发 PreRequestHandlerExecute 事件。

  15. 为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 BeginProcessRequest)。例如,如果该请求针对某页,则当前的页实例将处理该请求。

  16. 引发 PostRequestHandlerExecute 事件。

  17. 引发 ReleaseRequestState 事件。

  18. 引发 PostReleaseRequestState 事件。

  19. 如果定义了 Filter 属性,则执行响应筛选。

  20. 引发 UpdateRequestCache 事件。

  21. 引发 PostUpdateRequestCache 事件。

  22. 引发 EndRequest 事件。

上述UrlRoutingModule订阅了两个 HttpApplication 事件,PostResolveRequestCache 要比 PostMapRequestHandler 更早执行,先看PostResolveRequestCache 事件,他首先从首先从 RouteCollection 中获取一个 RouteData 对象。看看RouteCollection 属性:

    public System.Web.Routing.RouteCollection RouteCollection
        
{
            
get
            
{
                
if (this._routeCollection == null)
                
{
                    
this._routeCollection = RouteTable.Routes;
                }

                
return this._routeCollection;
            }

            
set
            
{
                
this._routeCollection = value;
            }

        }
 

看到了来自RouteTable,这不正是在Global.asax.cs 中添加的 Route 集合:

  protected void Application_Start(object sender, EventArgs e)
       
{
           RegisterRoutes(RouteTable.Routes);
           CreateDefaultUserIfNotExists();
       }
 

       
public static void RegisterRoutes(RouteCollection routes)
       
{
           
int iisVersion = Convert.ToInt32(ConfigurationManager.AppSettings["IISVersion"], System.Globalization.CultureInfo.InvariantCulture); 

           
if (iisVersion >= 7)
           
{
               RegisterRoutesForNewIIS(routes);
           }

           
else
           
{
               RegisterRoutesForOldIIS(routes);
           }

       }
 

      
private static void RegisterRoutesForNewIIS(ICollection<RouteBase> routes)
       
{
           routes.Add(
new Route("User/Login"new RouteValueDictionary(new { controller = "User", action = "Login" }), new MvcRouteHandler()));
           routes.Add(
new Route("User/Logout"new RouteValueDictionary(new { controller = "User", action = "Logout" }), new MvcRouteHandler()));
           routes.Add(
new Route("User/Signup"new RouteValueDictionary(new { controller = "User", action = "Signup" }), new MvcRouteHandler()));
           routes.Add(
new Route("User/SendPassword"new RouteValueDictionary(new { controller = "User", action = "SendPassword" }), new MvcRouteHandler())); 

           routes.Add(
new Route("Story/Detail/{id}"new RouteValueDictionary(new { controller = "Story", action = "Detail" }), new MvcRouteHandler()));
           routes.Add(
new Route("Story/Upcoming/{page}"new RouteValueDictionary(new { controller = "Story", action = "Upcoming" }), new MvcRouteHandler()));
           routes.Add(
new Route("Story/Search/{q}/{page}"new RouteValueDictionary(new { controller = "Story", action = "Search" }), new MvcRouteHandler())); 

           var defaults 
= new RouteValueDictionary (
                                                       
new
                                                       
{
                                                           controller 
= "Story",
                                                           action 
= "Category",
                                                           name 
= (string)null,
                                                           page 
= (int?)null
                                                       }

                                                   ); 

           routes.Add(
new Route("Story/Category/{page}", defaults, new MvcRouteHandler()));
           routes.Add(
new Route("Story/{action}/{name}/{page}", defaults, new MvcRouteHandler()));
           routes.Add(
new Route("{controller}/{action}/{id}", defaults, new MvcRouteHandler()));
           routes.Add(
new Route("Default.aspx", defaults, new MvcRouteHandler()));
       }
 

       
private static void RegisterRoutesForOldIIS(ICollection<RouteBase> routes)
       
{
           routes.Add(
new Route("User.mvc/Login"new RouteValueDictionary(new { controller = "User", action = "Login" }), new MvcRouteHandler()));
           routes.Add(
new Route("User.mvc/Logout"new RouteValueDictionary(new { controller = "User", action = "Logout" }), new MvcRouteHandler()));
           routes.Add(
new Route("User.mvc/Signup"new RouteValueDictionary(new { controller = "User", action = "Signup" }), new MvcRouteHandler()));
           routes.Add(
new Route("User.mvc/SendPassword"new RouteValueDictionary(new { controller = "User", action = "SendPassword" }), new MvcRouteHandler())); 

           routes.Add(
new Route("Story.mvc/Detail/{id}"new RouteValueDictionary(new { controller = "Story", action = "Detail" }), new MvcRouteHandler()));
           routes.Add(
new Route("Story.mvc/Upcoming/{page}"new RouteValueDictionary(new { controller = "Story", action = "Upcoming" }), new MvcRouteHandler()));
           routes.Add(
new Route("Story.mvc/Search/{q}/{page}"new RouteValueDictionary(new { controller = "Story", action = "Search" }), new MvcRouteHandler())); 

           var defaults 
= new RouteValueDictionary(
                                                       
new
                                                       
{
                                                           controller 
= "Story",
                                                           action 
= "Category",
                                                           name 
= (string)null,
                                                           page 
= (int?)null
                                                       }

                                                   ); 

           routes.Add(
new Route("Story.mvc/Category/{page}", defaults, new MvcRouteHandler()));
           routes.Add(
new Route("Story.mvc/{action}/{name}/{page}", defaults, new MvcRouteHandler()));
           routes.Add(
new Route("{controller}.mvc/{action}/{id}", defaults, new MvcRouteHandler()));
           routes.Add(
new Route("Default.aspx", defaults, new MvcRouteHandler()));
       }
 

 上述代码来自[翻译]使用asp.net mvc再造一个digg 第一部分的kigg。回到上文,在获取 RoteCollection 之后,通过调用 GetRouteData(context) 返回一个 RouteData 对象,该对象内部包含了我们注册 Route 时的相关设置,包括下面所需要的 MvcRouteHandler。接下来,该方法将 routeData 和上下文一起打包成 RequestContext,这就是为相关处理准备的上下文环境。通过调用 IRouteHandler.GetHttpHandler() 方法,终于到达流程的关键IHttpHandler(MvcHandler)。在WebForm中我们知道每一个页面都是一个HttpHandler,Asp.net mvc也不例外。

先来看看MvcRouteHandler:

 

namespace System.Web.Mvc {
    
using System.Web.Routing; 

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    
public class MvcRouteHandler : IRouteHandler {
        
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
            
return new MvcHandler(requestContext);
        }
 

        
IRouteHandler Members
    }

}
 

这里得到了MvcHandler:

 

namespace System.Web.Mvc {
    
using System.Globalization;
    
using System.Web.Mvc.Resources;
    
using System.Web.Routing;
    
using System.Web.SessionState; 

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    
public class MvcHandler : IHttpHandler, IRequiresSessionState {
        
private ControllerBuilder _controllerBuilder; 

        
public MvcHandler(RequestContext requestContext) {
            
if (requestContext == null{
                
throw new ArgumentNullException("requestContext");
            }

            RequestContext 
= requestContext;
        }
 

        
protected virtual bool IsReusable {
            
get {
                
// REVIEW: What's this?
                return false;
            }

        }
 

        
internal ControllerBuilder ControllerBuilder {
            
get {
                
if (_controllerBuilder == null{
                    _controllerBuilder 
= ControllerBuilder.Current;
                }

                
return _controllerBuilder;
            }

            
set {
                _controllerBuilder 
= value;
            }

        }
 

        
public RequestContext RequestContext {
            
get;
            
private set;
        }
 

        
protected virtual void ProcessRequest(HttpContext httpContext) {
            HttpContextBase iHttpContext 
= new HttpContextWrapper2(httpContext);
            ProcessRequest(iHttpContext);
        }
 

        
protected internal virtual void ProcessRequest(HttpContextBase httpContext) {
            
// Get the controller type
            string controllerName = RequestContext.RouteData.GetRequiredString("controller"); 

            
// Instantiate the controller and call Execute
            IControllerFactory factory = ControllerBuilder.GetControllerFactory();
            IController controller 
= factory.CreateController(RequestContext, controllerName);
            
if (controller == null{
                
throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        MvcResources.ControllerBuilder_FactoryReturnedNull,
                        factory.GetType(),
                        controllerName));
            }

            
try {
                ControllerContext controllerContext 
= new ControllerContext(RequestContext, controller);
                controller.Execute(controllerContext);
            }

            
finally {
                factory.DisposeController(controller);
            }

        }
 

        
IHttpHandler Members
    }

}
到了这一步,MVC 框架已经准备好了相应的执行场景,接下来就是修改默认(指WebForm)的执行流程了。
             RequestData data2 = new RequestData();
                data2.OriginalPath 
= context.Request.Path;
                data2.HttpHandler 
= httpHandler;
                context.Items[_requestDataKey] 
= data2;
                context.RewritePath(
"~/UrlRouting.axd"); 

OnApplicationPostMapRequestHandler 被执行。在 PostMapRequestHandler 中,它提取了前面预先准备好的上下文,并修改了 HttpContext.Handler,使得 MvcHandler 接管默认的WebForm的HttpHandler,才是执行ASP.NET MVC的流程。现在来继续看MvcHandler。

首先从 RouteData 中提取 Controller 的名字(这个名字是我们在 Global.asax.cs RegisterRoutes 中注册 Route 时提供的),然后获取 ControllerFactory,只不过这里面专门提供了一个 ControllerBuilder。

internal ControllerBuilder ControllerBuilder {
            
get {
                
if (_controllerBuilder == null{
                    _controllerBuilder 
= ControllerBuilder.Current;
                }

                
return _controllerBuilder;
            }

            
set {
                _controllerBuilder 
= value;
            }

        }
 

如果我们自定义MvcHandler,则需要好好的看看ControllerBuilder.Current:

namespace System.Web.Mvc {
    
using System;
    
using System.Diagnostics.CodeAnalysis;
    
using System.Globalization;
    
using System.Web;
    
using System.Web.Mvc.Resources; 

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    
public class ControllerBuilder {
        
private static ControllerBuilder _instance = new ControllerBuilder();
        
private Func<IControllerFactory> _factoryThunk; 

        
public ControllerBuilder() {
            SetControllerFactory(
new DefaultControllerFactory());
        }
 

        
public static ControllerBuilder Current {
            
get {
                
return _instance;
            }

        }
 

        [SuppressMessage(
"Microsoft.Design""CA1024:UsePropertiesWhereAppropriate",
            Justification 
= "Calling method multiple times might return different objects.")]
        
public IControllerFactory GetControllerFactory() {
            IControllerFactory controllerFactoryInstance 
= _factoryThunk();
            
return controllerFactoryInstance;
        }
 

        
public void SetControllerFactory(IControllerFactory controllerFactory) {
            
if (controllerFactory == null{
                
throw new ArgumentNullException("controllerFactory");
            }
 

            _factoryThunk 
= () => controllerFactory;
        }
 

        
public void SetControllerFactory(Type controllerFactoryType) {
            
if (controllerFactoryType == null{
                
throw new ArgumentNullException("controllerFactoryType");
            }

            
if (!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType)) {
                
throw new ArgumentException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        MvcResources.ControllerBuilder_MissingIControllerFactory,
                        controllerFactoryType),
                    
"controllerFactoryType");
            }
 

            _factoryThunk 
= delegate() {
                
try {
                    
return (IControllerFactory) Activator.CreateInstance(controllerFactoryType);
                }

                
catch (Exception ex) {
                    
throw new InvalidOperationException(
                        String.Format(
                            CultureInfo.CurrentUICulture,
                            MvcResources.ControllerBuilder_ErrorCreatingControllerFactory,
                            controllerFactoryType),
                        ex);
                }

            }
;
        }

    }

}
 

(1) 通常情况下,返回一个默认的 DefaultControllerFactory 实例。
(2) 我们可以在 Application_Start 中通过 ControllerBuilder.Current.SetControllerFactory 方法来注册一个我们自定义的工厂。
(3) 核心代码: factory = (IControllerFactory)Activator.CreateInstance(controllerFactoryType),通过反射创建IControllerFactory;

回到MvcHandler的 ProcessRequest ,DefaultControllerFactory.CreateController(RequestContext, requiredString) 来返回 IController 实例。下面看看DefaultControllerFactory的代码:

通过反射来创建 Controller 实例,GetControllerType 里面做了些缓存处理,以此来避免频繁使用反射造成的性能问题。继续

MvcHandler.ProcessRequest(),在得到控制器实例后,MvcHandler 开始了调用 Controller.Execute() 来进一步后续操作,同时对其上下文进一步封装,除了前面创建的 RequestContext,还加上了当前这个 Controller 对象的引用,类名叫ControllerContext。

 

namespace System.Web.Mvc {
    
using System.Web.Routing; 

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    
public class ControllerContext : RequestContext {
        
public ControllerContext(HttpContextBase httpContext, RouteData routeData, IController controller)
            : 
base(httpContext, routeData) {
            
if (controller == null{
                
throw new ArgumentNullException("controller");
            }

            Controller 
= controller;
        }
 

        
public ControllerContext(RequestContext requestContext, IController controller)
            : 
this(GetRequestContext(requestContext).HttpContext, GetRequestContext(requestContext).RouteData, controller) {
        }
 

        
public IController Controller {
            
get;
            
private set;
        }
 

        
internal static RequestContext GetRequestContext(RequestContext requestContext) {
            
if (requestContext == null{
                
throw new ArgumentNullException("requestContext");
            }

            
return requestContext;
        }

    }

}
 

继续看IController的默认实现类Controller:

 

namespace System.Web.Mvc {
    
using System;
    
using System.Collections.Generic;
    
using System.ComponentModel;
    
using System.Diagnostics.CodeAnalysis;
    
using System.Globalization;
    
using System.Reflection;
    
using System.Security.Principal;
    
using System.Web;
    
using System.Web.Mvc.Resources;
    
using System.Web.Routing; 

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    
public class Controller : IController {
        
private RouteCollection _routeCollection;
        
private IDictionary<stringobject> _viewData;
        
private IViewEngine _viewEngine; 

        
public ControllerContext ControllerContext {
            
get;
            
set;
        }
 

        
public HttpContextBase HttpContext {
            
get {
                
return ControllerContext == null ? null : ControllerContext.HttpContext;
            }

        }
 

        
public HttpRequestBase Request {
            
get {
                
return HttpContext == null ? null : HttpContext.Request;
            }

        }
 

        
public HttpResponseBase Response {
            
get {
                
return HttpContext == null ? null : HttpContext.Response;
            }

        }
 

        
internal RouteCollection RouteCollection {
            
get {
                
if (_routeCollection == null{
                    _routeCollection 
= RouteTable.Routes;
                }

                
return _routeCollection;
            }

            
set {
                _routeCollection 
= value;
            }

        }
 

        
public RouteData RouteData {
            
get {
                
return ControllerContext == null ? null : ControllerContext.RouteData;
            }

        }
 

        
public HttpServerUtilityBase Server {
            
get {
                
return HttpContext == null ? null : HttpContext.Server;
            }

        }
 

        [System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Usage""CA2227:CollectionPropertiesShouldBeReadOnly",
            Justification 
= "This property is settable so that unit tests can provide mock implementations.")]
        
public TempDataDictionary TempData {
            
get;
            
set;
        }
 

        
public IPrincipal User {
            
get {
                
return HttpContext == null ? null : HttpContext.User;
            }

        }
 

        
public IDictionary<stringobject> ViewData {
            
get {
                
if (_viewData == null{
                    _viewData 
= new Dictionary<stringobject>();
                }

                
return _viewData;
            }

        }
 

        
public IViewEngine ViewEngine {
            
get {
                
return _viewEngine ?? new WebFormViewEngine();
            }

            
set {
                
if (value == null{
                    
throw new ArgumentNullException("value");
                }

                _viewEngine 
= value;
            }

        }
 

        
private static object ConvertParameterType(object value, Type destinationType, string parameterName, string actionName) {
            
if (value == null || value.GetType() == destinationType) {
                
return value;
            }
 

            TypeConverter converter 
= TypeDescriptor.GetConverter(destinationType);
            
bool canConvertFrom = converter.CanConvertFrom(value.GetType());
            
if (!canConvertFrom) {
                converter 
= TypeDescriptor.GetConverter(value.GetType());
            }

            
if (!(canConvertFrom || converter.CanConvertTo(destinationType))) {
                
throw new InvalidOperationException(String.Format(
                    CultureInfo.CurrentUICulture,
                    MvcResources.Controller_CannotConvertParameter,
                    parameterName, actionName, value, destinationType));
            }

            
try {
                
return canConvertFrom ? converter.ConvertFrom(value) : converter.ConvertTo(value, destinationType);
            }

            
catch (Exception ex) {
                
throw new InvalidOperationException(String.Format(
                    CultureInfo.CurrentUICulture,
                    MvcResources.Controller_CannotConvertParameter,
                    parameterName, actionName, value, destinationType),
                    ex);
            }

        }
 

        
protected internal virtual void Execute(ControllerContext controllerContext) {
            
if (controllerContext == null{
                
throw new ArgumentNullException("controllerContext");
            }
 

            ControllerContext 
= controllerContext;
            TempData 
= new TempDataDictionary(controllerContext.HttpContext); 

            
string actionName = RouteData.GetRequiredString("action");
            
if (!InvokeAction(actionName)) {
                HandleUnknownAction(actionName);
            }

        }
 

        
protected internal virtual void HandleUnknownAction(string actionName) {
            
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_UnknownAction, actionName));
        }
 

        
protected internal bool InvokeAction(string actionName) {
            
return InvokeAction(actionName, new RouteValueDictionary());
        }
 

        
protected internal virtual bool InvokeAction(string actionName, RouteValueDictionary values) {
            
if (String.IsNullOrEmpty(actionName)) {
                
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
            }
 

            
// We have to loop through all the methods to make sure there isn't
            
// a conflict. If we stop the loop the first time we find a match
            
// we might miss some error cases. 

            MemberInfo[] membInfos 
= GetType().GetMember(actionName, MemberTypes.Method,
                BindingFlags.IgnoreCase 
| BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
            MethodInfo foundMatch 
= null

            
foreach (MemberInfo memberInfo in membInfos) {
                MethodInfo mi 
= (MethodInfo)memberInfo; 

                
// 1) Action methods must not have the non-action attribute in their inheritance chain, and
                
// 2) special methods like constructors, property accessors, and event accessors cannot be action methods, and
                
// 3) methods originally defined on Object (like ToString) or Controller cannot be action methods.
                if (!mi.IsDefined(typeof(NonActionAttribute), true&&
                    
!mi.IsSpecialName &&
                    mi.DeclaringType.IsSubclassOf(
typeof(Controller))) {
                    
if (foundMatch != null{
                        
throw new InvalidOperationException(
                            String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_MoreThanOneAction, actionName, GetType()));
                    }

                    foundMatch 
= mi;
                }

            }
 

            
if (foundMatch != null{
                InvokeActionMethod(foundMatch, values);
                
return true;
            }

            
return false;
        }
 

        [SuppressMessage(
"Microsoft.Design""CA1011:ConsiderPassingBaseTypesAsParameters",
            Justification 
= "We use MethodInfo since it represents only methods and not constructors." +
            
"This method only makes sense for use with methods.")]
        
protected internal virtual void InvokeActionMethod(MethodInfo methodInfo, RouteValueDictionary values) {
            
if (methodInfo == null{
                
throw new ArgumentNullException("methodInfo");
            }

            
if (values == null{
                values 
= new RouteValueDictionary();
            }

            
if (methodInfo.ContainsGenericParameters) {
                
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_ActionCannotBeGeneric, methodInfo.Name));
            }
 

            ParameterInfo[] methodParameters 
= methodInfo.GetParameters();
            
object[] parameterValues = null;
            
if (methodParameters.Length > 0

                parameterValues 
= new object[methodParameters.Length];
                
for (int i = 0; i < methodParameters.Length; i++{
                    ParameterInfo pi 
= methodParameters[i]; 

                    
if (pi.IsOut || pi.ParameterType.IsByRef) {
                        
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_ReferenceParametersNotSupported, pi.Name, methodInfo.Name));
                    }
 

                    
bool valueRequired = true;
                    
if (pi.ParameterType.IsClass) {
                        
// Classes (ref types) don't require values since we can pass in null
                        valueRequired = false;
                    }

                    
else {
                        
if ((pi.ParameterType.IsGenericType && !pi.ParameterType.IsGenericTypeDefinition) &&
                            (pi.ParameterType.GetGenericTypeDefinition() 
== typeof(Nullable<>))) {
                            
// Nullable types don't require values since we can pass in null
                            valueRequired = false;
                        }

                    }
 

                    
// Try to get a value for the parameter. We use this order of precedence:
                    
// 1. Explicitly-provided extra parameters in the call to InvokeAction()
                    
// 2. Values from the RouteData (could be from the typed-in URL or from the route's default values)
                    
// 3. Request values (query string, form post data, cookie)
                    object parameterValue = null;
                    
if (!values.TryGetValue(methodParameters[i].Name, out parameterValue)) {
                        
if (RouteData == null || !RouteData.Values.TryGetValue(methodParameters[i].Name, out parameterValue)) {
                            
if (Request != null{
                                parameterValue 
= Request[methodParameters[i].Name];
                            }

                        }

                    }
 

                    
if (parameterValue == null && valueRequired) {
                        
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_MissingParameter, pi.Name, methodInfo.Name));
                    }
 

                    
try {
                        parameterValues[i] 
= ConvertParameterType(parameterValue, methodParameters[i].ParameterType, methodParameters[i].Name, methodInfo.Name);
                    }

                    
catch (Exception ex) {
                        
// Parameter value conversion errors are acceptable unless the value is required
                        if (valueRequired) {
                            
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_MissingParameter, pi.Name, methodInfo.Name), ex);
                        }

                    }

                }

            }
 

            InvokeActionMethodFilters(methodInfo, () 
=> methodInfo.Invoke(this, parameterValues));
        }
 

        
private void InvokeActionMethodFilters(MethodInfo methodInfo, Action continuation) 

            
// filters should execute in this order:
            
// controller virtual overrides -> controller base attributes -> controller type attributes ->
            
//   base action method attributes -> action method attributes 

            List
<ActionFilterAttribute> filters = new List<ActionFilterAttribute>() {
                
new ControllerActionFilter(this)
            }


            Stack
<MemberInfo> memberChain = new Stack<MemberInfo>();
            Type curType 
= GetType();
            
while (curType != null{
                memberChain.Push(curType);
                curType 
= curType.BaseType;
            }
 

            List
<ActionFilterAttribute> sortedClassFilters = SortActionFilters(memberChain);
            filters.AddRange(sortedClassFilters);
            List
<ActionFilterAttribute> sortedMethodFilters = PrepareMethodActionFilters(methodInfo);
            filters.AddRange(sortedMethodFilters); 

            FilterContext context 
= new FilterContext(ControllerContext, methodInfo);
            ActionFilterExecutor executor 
= new ActionFilterExecutor(filters, context, continuation);
            executor.Execute();
        }
 

        
protected virtual void OnActionExecuted(FilterExecutedContext filterContext) {
        }
 

        
protected virtual void OnActionExecuting(FilterExecutingContext filterContext) {
        }
 

        
internal static List<ActionFilterAttribute> PrepareMethodActionFilters(MethodInfo methodInfo) 

            Stack
<MemberInfo> memberChain = new Stack<MemberInfo>();
            memberChain.Push(methodInfo); 

            MethodInfo baseMethod 
= methodInfo.GetBaseDefinition();
            Type curType 
= methodInfo.DeclaringType.BaseType;
            
while (true{
                MemberInfo[] membInfos 
= curType.GetMember(methodInfo.Name, MemberTypes.Method,
                    BindingFlags.IgnoreCase 
| BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
                MethodInfo foundMatch 
= null;
                
foreach (MemberInfo memberInfo in membInfos) {
                    MethodInfo mi 
= (MethodInfo)memberInfo;
                    
if (mi.GetBaseDefinition() == baseMethod && mi.DeclaringType == curType) {
                        foundMatch 
= mi;
                        
break;
                    }

                }

                
if (foundMatch == null{
                    
break;
                }

                memberChain.Push(foundMatch);
                curType 
= curType.BaseType;
            }
 

            
return SortActionFilters(memberChain);
        }
 

        
protected virtual void RedirectToAction(RouteValueDictionary values) {
            VirtualPathData vpd 
= RouteCollection.GetVirtualPath(ControllerContext, values);
            
string target = null;
            
if (vpd != null{
                target 
= vpd.VirtualPath;
            }

            HttpContext.Response.Redirect(target);
        }
 

        
protected void RedirectToAction(string actionName) {
            
if (String.IsNullOrEmpty(actionName)) {
                
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
            }

            RouteValueDictionary valuesDictionary 
= new RouteValueDictionary();
            valuesDictionary.Add(
"action", actionName);
            RedirectToAction(valuesDictionary);
        }
 

        
protected void RedirectToAction(string actionName, string controllerName) {
            
if (String.IsNullOrEmpty(actionName)) {
                
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
            }

            
if (String.IsNullOrEmpty(controllerName)) {
                
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
            }

            RouteValueDictionary valuesDictionary 
= new RouteValueDictionary();
            valuesDictionary.Add(
"action", actionName);
            valuesDictionary.Add(
"controller", controllerName);
            RedirectToAction(valuesDictionary);
        }
 

        
protected void RenderView(string viewName) {
            RenderView(viewName, String.Empty, ViewData);
        }
 

        
protected void RenderView(string viewName, string masterName) {
            RenderView(viewName, masterName, ViewData);
        }
 

        
protected void RenderView(string viewName, object viewData) {
            RenderView(viewName, String.Empty, viewData);
        }
 

        
protected virtual void RenderView(string viewName, string masterName, object viewData) {
            ViewContext viewContext 
= new ViewContext(ControllerContext, viewName, masterName, viewData, TempData);
            ViewEngine.RenderView(viewContext);
        }
 

        
private static List<ActionFilterAttribute> SortActionFilters(Stack<MemberInfo> memberChain) {
            List
<ActionFilterAttribute> filters = new List<ActionFilterAttribute>(); 

            
foreach (MemberInfo member in memberChain) {
                ActionFilterAttribute[] attrs 
= (ActionFilterAttribute[])member.GetCustomAttributes(typeof(ActionFilterAttribute), false /* inherit */);
                SortedList
<int, ActionFilterAttribute> orderedFilters = new SortedList<int, ActionFilterAttribute>();
                
foreach (ActionFilterAttribute filter in attrs) {
                    
// filters are allowed to have the same order only if the order is -1.  in that case,
                    
// they are processed before explicitly ordered filters but in no particular order in
                    
// relation to one another.
                    if (filter.Order >= 0{
                        
if (orderedFilters.ContainsKey(filter.Order)) {
                            
throw new InvalidOperationException(
                                String.Format(
                                    CultureInfo.CurrentUICulture,
                                    MvcResources.ActionFilter_DuplicateOrder,
                                    member,
                                    filter.Order));
                        }

                        orderedFilters.Add(filter.Order, filter);
                    }

                    
else {
                        filters.Add(filter);
                    }

                }

                filters.AddRange(orderedFilters.Values);
            }
 

            
return filters;
        }
 

        
IController Members 

        
private sealed class ControllerActionFilter : ActionFilterAttribute 

            
private Controller _controller; 

            
public ControllerActionFilter(Controller controller) {
                _controller 
= controller;
            }
 

            
public override void OnActionExecuted(FilterExecutedContext filterContext) {
                _controller.OnActionExecuted(filterContext);
            }
 

            
public override void OnActionExecuting(FilterExecutingContext filterContext) {
                _controller.OnActionExecuting(filterContext);
            }
 

        }
 

    }

}
 

获取 Action 的名字,然后开始执行 InvokeAction,如果找不到Action,则调用HandleUnknownAction,这是一个虚拟方法,可以在子类中重写,默认是抛出一个异常InvalidOperationException。

 

          protected internal virtual void Execute(ControllerContext controllerContext) {
            
if (controllerContext == null{
                
throw new ArgumentNullException("controllerContext");
            }
 

            ControllerContext 
= controllerContext;
            TempData 
= new TempDataDictionary(controllerContext.HttpContext); 

            
string actionName = RouteData.GetRequiredString("action");
            
if (!InvokeAction(actionName)) {
                HandleUnknownAction(actionName);
            }

        }
 

详细看看InvokeAction方式的执行:

 

protected internal bool InvokeAction(string actionName) {
            
return InvokeAction(actionName, new RouteValueDictionary());
        }
 

        
protected internal virtual bool InvokeAction(string actionName, RouteValueDictionary values) {
            
if (String.IsNullOrEmpty(actionName)) {
                
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
            }
 

            
// We have to loop through all the methods to make sure there isn't
            
// a conflict. If we stop the loop the first time we find a match
            
// we might miss some error cases. 

            MemberInfo[] membInfos 
= GetType().GetMember(actionName, MemberTypes.Method,
                BindingFlags.IgnoreCase 
| BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
            MethodInfo foundMatch 
= null

            
foreach (MemberInfo memberInfo in membInfos) {
                MethodInfo mi 
= (MethodInfo)memberInfo; 

                
// 1) Action methods must not have the non-action attribute in their inheritance chain, and
                
// 2) special methods like constructors, property accessors, and event accessors cannot be action methods, and
                
// 3) methods originally defined on Object (like ToString) or Controller cannot be action methods.
                if (!mi.IsDefined(typeof(NonActionAttribute), true&&
                    
!mi.IsSpecialName &&
                    mi.DeclaringType.IsSubclassOf(
typeof(Controller))) {
                    
if (foundMatch != null{
                        
throw new InvalidOperationException(
                            String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_MoreThanOneAction, actionName, GetType()));
                    }

                    foundMatch 
= mi;
                }

            }
 

            
if (foundMatch != null{
                InvokeActionMethod(foundMatch, values);
                
return true;
            }

            
return false;
        }
 

它通过反射获取所有同名 Action 方法信息;其次,它过滤掉所有有 NonActionAttribute 和 IsSpecialName 标记的方法;第三,当同名有效 Action 被重载时它会抛出异常(提示Controller_MoreThanOneAction),继续调用InvokeActionMethod:

[SuppressMessage("Microsoft.Design""CA1011:ConsiderPassingBaseTypesAsParameters",
     Justification 
= "We use MethodInfo since it represents only methods and not constructors." +
     
"This method only makes sense for use with methods.")]
protected internal virtual void InvokeActionMethod(MethodInfo methodInfo, RouteValueDictionary values) {
     
if (methodInfo == null{
         
throw new ArgumentNullException("methodInfo");
     }

     
if (values == null{
         values 
= new RouteValueDictionary();
     }

     
if (methodInfo.ContainsGenericParameters) {
         
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_ActionCannotBeGeneric, methodInfo.Name));
     }
 

     ParameterInfo[] methodParameters 
= methodInfo.GetParameters();
     
object[] parameterValues = null;
     
if (methodParameters.Length > 0

         parameterValues 
= new object[methodParameters.Length];
         
for (int i = 0; i < methodParameters.Length; i++{
             ParameterInfo pi 
= methodParameters[i]; 

             
if (pi.IsOut || pi.ParameterType.IsByRef) {
                 
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_ReferenceParametersNotSupported, pi.Name, methodInfo.Name));
             }
 

             
bool valueRequired = true;
             
if (pi.ParameterType.IsClass) {
                 
// Classes (ref types) don't require values since we can pass in null
                 valueRequired = false;
             }

             
else {
                 
if ((pi.ParameterType.IsGenericType && !pi.ParameterType.IsGenericTypeDefinition) &&
                     (pi.ParameterType.GetGenericTypeDefinition() 
== typeof(Nullable<>))) {
                     
// Nullable types don't require values since we can pass in null
                     valueRequired = false;
                 }

             }
 

             
// Try to get a value for the parameter. We use this order of precedence:
             
// 1. Explicitly-provided extra parameters in the call to InvokeAction()
             
// 2. Values from the RouteData (could be from the typed-in URL or from the route's default values)
             
// 3. Request values (query string, form post data, cookie)
             object parameterValue = null;
             
if (!values.TryGetValue(methodParameters[i].Name, out parameterValue)) {
                 
if (RouteData == null || !RouteData.Values.TryGetValue(methodParameters[i].Name, out parameterValue)) {
                     
if (Request != null{
                         parameterValue 
= Request[methodParameters[i].Name];
                     }

                 }

             }
 

             
if (parameterValue == null && valueRequired) {
                 
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_MissingParameter, pi.Name, methodInfo.Name));
             }
 

             
try {
                 parameterValues[i] 
= ConvertParameterType(parameterValue, methodParameters[i].ParameterType, methodParameters[i].Name, methodInfo.Name);
             }

             
catch (Exception ex) {
                 
// Parameter value conversion errors are acceptable unless the value is required
                 if (valueRequired) {
                     
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_MissingParameter, pi.Name, methodInfo.Name), ex);
                 }

             }

         }

     }
 

     InvokeActionMethodFilters(methodInfo, () 
=> methodInfo.Invoke(this, parameterValues));
}
这个方法很复杂,有大量的代码是参数的分解 ,只有最后一行是关键的。

InvokeActionMethodFilters(methodInfo, () => methodInfo.Invoke(this, parameterValues));

这行代码将 Action 的调用作为一个委托,连同反射信息传递给 InvokeActionMethodFilters。

 

private void InvokeActionMethodFilters(MethodInfo methodInfo, Action continuation) 

           
// filters should execute in this order:
           
// controller virtual overrides -> controller base attributes -> controller type attributes ->
           
//   base action method attributes -> action method attributes 

           List
<ActionFilterAttribute> filters = new List<ActionFilterAttribute>() {
               
new ControllerActionFilter(this)
           }


           Stack
<MemberInfo> memberChain = new Stack<MemberInfo>();
           Type curType 
= GetType();
           
while (curType != null{
               memberChain.Push(curType);
               curType 
= curType.BaseType;
           }
 

           List
<ActionFilterAttribute> sortedClassFilters = SortActionFilters(memberChain);
           filters.AddRange(sortedClassFilters);
           List
<ActionFilterAttribute> sortedMethodFilters = PrepareMethodActionFilters(methodInfo);
           filters.AddRange(sortedMethodFilters); 

           FilterContext context 
= new FilterContext(ControllerContext, methodInfo);
           ActionFilterExecutor executor 
= new ActionFilterExecutor(filters, context, continuation);
           executor.Execute();
       }
 

这个方法首先将默认的过滤器 ControllerActionFilter 加到列表,然后提取所有继承层次上基类的过滤器特性。最后将这些过滤器集合、过滤上下文,连同前一个方法传递进来的 Action 执行委托(continuation) 再次转交给了一个 ActionFilterExecutor 对象实例,并调用其 Execute 方法。

ExecuteRecursive使用了递归算法,通过迭代器 MoveNext() 方法提取一个过滤器对象,执行其 OnActionExecuting 方法。 如果该方法设置了 filterContext.Cancel = true,则放弃后续执行代码。这种机制为我们提供了更好的控制,例如用它来实现伪静态页。一层一层调用所有的 ActionFilterAttribute.OnActionExecuting 方法,直到 MoveNext() == false。 在最后一次递归调用时,由于 enumerator.MoveNext() == false, _continuation() 方法被执行。这个就是前面给传递过来的 Action 方法委托,Action 方法总算是执行了。 在 Action 委托执行完成后,递归调逐级往上回溯,直到最初那个方法堆栈。这样所有ActionFilterAttribute.OnActionExecuted 也被执行完成。

到此开始进入最后的视图呈现阶段,可以把数据呈现到视图上,Controller 提供了几个重载的 RenderView() 来完成这个工作。

 

       protected void RenderView(string viewName) {
           RenderView(viewName, String.Empty, ViewData);
       }
 

       
protected void RenderView(string viewName, string masterName) {
           RenderView(viewName, masterName, ViewData);
       }
 

       
protected void RenderView(string viewName, object viewData) {
           RenderView(viewName, String.Empty, viewData);
       }
 

       
protected virtual void RenderView(string viewName, string masterName, object viewData) {
           ViewContext viewContext 
= new ViewContext(ControllerContext, viewName, masterName, viewData, TempData);
           ViewEngine.RenderView(viewContext);
       }
 

将一路传递过来的相关 "数据" (上下文)ControllerContext 再次包装成ViewContext 。当然,这次依然会多出些东西,里面就有我们向视图传递的数据 —— viewData 和tempData。作为默认选择,MVC 创建 WebForm 视图引擎来展示结果。其他的视图引擎可以去看mvccontrib,这个项目就是整合:Castle WindsorStructureMapSpring.NET 等IoC框架以及视图引擎,包括Castle MonoRail所用的NVelocityView视图引擎、NHamlView视图引擎、XsltViewEngine视图引擎等等。

继续看这个 WebFormViewEngine:



namespace System.Web.Mvc {
    
using System;
    
using System.Globalization;
    
using System.Web.Compilation;
    
using System.Web.Resources;
    
using System.Web.Routing;
    
using System.Web.Mvc.Resources; 

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    
public class WebFormViewEngine : IViewEngine {
        
private IBuildManager _buildManager;
        
private IViewLocator _viewLocator; 

        
internal IBuildManager BuildManager {
            
get {
                
if (_buildManager == null{
                    _buildManager 
= new BuildManagerWrapper();
                }

                
return _buildManager;
            }

            
set {
                _buildManager 
= value;
            }

        }
 

        
public IViewLocator ViewLocator {
            
get {
                
if (_viewLocator == null{
                    _viewLocator 
= new WebFormViewLocator();
                }

                
return _viewLocator;
            }

            
set {
                _viewLocator 
= value;
            }

        }
 

        
protected virtual void RenderView(ViewContext viewContext) {
            
if (viewContext == null{
                
throw new ArgumentNullException("viewContext");
            }
 

            
string viewPath = ViewLocator.GetViewLocation(viewContext, viewContext.ViewName);
            
if (String.IsNullOrEmpty(viewPath)) {
                
throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        MvcResources.WebFormViewEngine_ViewNotFound,
                        viewContext.ViewName));
            }

            
object viewInstance = BuildManager.CreateInstanceFromVirtualPath(viewPath, typeof(object));
            
if (viewInstance == null{
                
throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,
                        viewPath));
            }
 

            ViewPage viewPage 
= viewInstance as ViewPage; 

            
if (viewPage != null

                
if (!String.IsNullOrEmpty(viewContext.MasterName)) {
                    
string masterLocation = ViewLocator.GetMasterLocation(viewContext, viewContext.MasterName);
                    
if (String.IsNullOrEmpty(masterLocation)) {
                        
throw new InvalidOperationException(
                            String.Format(
                                CultureInfo.CurrentUICulture,
                                MvcResources.WebFormViewEngine_MasterNotFound,
                                viewContext.MasterName));
                    }
 

                    
// We don't set the page's MasterPageFile directly since it will get
                    
// overwritten by a statically-defined value. In ViewPage we wait until
                    
// the PreInit phase until we set the new value.
                    viewPage.MasterLocation = masterLocation;
                }

                viewPage.SetViewData(viewContext.ViewData);
                viewPage.RenderView(viewContext);
            }

            
else {
                ViewUserControl viewUserControl 
= viewInstance as ViewUserControl;
                
if (viewUserControl != null{
                    
if (!String.IsNullOrEmpty(viewContext.MasterName)) {
                        
throw new InvalidOperationException(MvcResources.WebFormViewEngine_UserControlCannotHaveMaster);
                    }
 

                    viewUserControl.SetViewData(viewContext.ViewData);
                    viewUserControl.RenderView(viewContext);
                }

                
else {
                    
throw new InvalidOperationException(
                        String.Format(
                            CultureInfo.CurrentUICulture,
                            MvcResources.WebFormViewEngine_WrongViewBase,
                            viewPath));
                }

            }

        }
 

        
IViewEngine Members
    }

}
 

首先会创建一个 WebFormViewLocator 对象来获取视图存放路径。

namespace System.Web.Mvc {
    public class WebFormViewLocator : ViewLocator {
        public WebFormViewLocator() {
            ViewLocationFormats = new[] {
                "~/Views/{1}/{0}.aspx",
                "~/Views/{1}/{0}.ascx",
                "~/Views/Shared/{0}.aspx",
                "~/Views/Shared/{0}.ascx"
            };
            MasterLocationFormats = new[] {
                "~/Views/{1}/{0}.master",
                "~/Views/Shared/{0}.master"
            };
        }
    }
}

在获取路径后,WebFormViewEngine 通过一个包装类 BuildManagerWrapper 间接调用 System.Web.Compilation.BuildManager 的静态方法 CreateInstanceFromVirtualPath() 将视图进行编译,并返回一个对象实例。(System.Web.Compilation.BuildManager BuildManager 类管理应用程序的程序集和页的编译过程),后面通过 as 转换结果来判断视图是 ViewPage 还是 ViewUserControl。ViewPage 继承自我们所熟悉的 System.Web.UI.Page,它的 RenderView() 方法也就是完成WebForm的 Page.ProcessRequest() 的处理而已,也就完成了对WebForm模型的置换。

下面看看ViewPage和ViewUserControl的代码:

namespace System.Web.Mvc {
    using System.Web.UI;

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class ViewPage : Page, IViewDataContainer {
        private string _masterLocation;
        private object _viewData;

        public string MasterLocation {
            get {
                return _masterLocation ?? String.Empty;
            }
            set {
                _masterLocation = value;
            }
        }

        public AjaxHelper Ajax {
            get;
            set;
        }

        public HtmlHelper Html {
            get;
            set;
        }

        public TempDataDictionary TempData {
            get {
                return ViewContext.TempData;
            }
        }

        public UrlHelper Url {
            get;
            set;
        }

        public ViewContext ViewContext {
            get;
            private set;
        }

        public ViewData ViewData {
            get {
                return new ViewData(_viewData);
            }
        }

        public HtmlTextWriter Writer {
            get;
            private set;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")]
        protected override void OnPreInit(EventArgs e) {
            base.OnPreInit(e);

            if (!String.IsNullOrEmpty(MasterLocation)) {
                MasterPageFile = MasterLocation;
            }
        }

        public virtual void RenderView(ViewContext viewContext) {
            ViewContext = viewContext;
            Ajax = new AjaxHelper(viewContext);
            Html = new HtmlHelper(viewContext);
            Url = new UrlHelper(viewContext);

            ProcessRequest(HttpContext.Current);
        }

        protected override void Render(HtmlTextWriter writer) {
            Writer = writer;
            try {
                base.Render(writer);
            }
            finally {
                Writer = null;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
            Justification = "There is already a ViewData property and it has a slightly different meaning.")]
        protected internal virtual void SetViewData(object viewData) {
            _viewData = viewData;
        }

        #region IViewDataContainer Members
        object IViewDataContainer.ViewData {
            get {
                return _viewData;
            }
        }
        #endregion
    }
}

 

namespace System.Web.Mvc {
    using System.ComponentModel;
    using System.Globalization;
    using System.Web.Resources;
    using System.Web.UI;
    using System.Web.Mvc.Resources;

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class ViewUserControl : UserControl, IViewDataContainer {
        private string _viewDataKey;
        private object _viewData;

        public AjaxHelper Ajax {
            get {
                return ViewPage.Ajax;
            }
        }

        public HtmlHelper Html {
            get {
                return ViewPage.Html;
            }
        }

        public TempDataDictionary TempData {
            get {
                return ViewPage.TempData;
            }
        }

        public UrlHelper Url {
            get {
                return ViewPage.Url;
            }
        }

        public ViewData ViewData {
            get {
                EnsureViewData();
                return new ViewData(_viewData);
            }
        }

        public ViewContext ViewContext {
            get {
                return ViewPage.ViewContext;
            }
        }

        [DefaultValue("")]
        public string ViewDataKey {
            get {
                return _viewDataKey ?? String.Empty;
            }
            set {
                _viewDataKey = value;
            }
        }

        private ViewPage ViewPage {
            get {
                ViewPage viewPage = Page as ViewPage;
                if (viewPage == null) {
                    throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.ViewUserControl_RequiresViewPage));
                }
                return viewPage;
            }
        }

        public HtmlTextWriter Writer {
            get {
                return ViewPage.Writer;
            }
        }

        private void EnsureViewData() {
            // Get the ViewData for this ViewUserControl, optionally using the specified ViewDataKey
            if (_viewData != null) {
                return;
            }
            IViewDataContainer vdc = GetViewDataContainer(this);
            if (vdc == null) {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        MvcResources.ViewUserControl_RequiresViewDataProvider,
                        AppRelativeVirtualPath));
            }
            if (String.IsNullOrEmpty(ViewDataKey)) {
                _viewData = vdc.ViewData;
            }
            else {
                _viewData = DataBinder.Eval(vdc.ViewData, ViewDataKey);
            }
        }

        private static IViewDataContainer GetViewDataContainer(Control control) {
            // Walk up the control hierarchy until we find someone that implements IViewDataContainer
            while (control != null) {
                control = control.Parent;
                IViewDataContainer vdc = control as IViewDataContainer;
                if (vdc != null) {
                    return vdc;
                }
            }
            return null;
        }

        public virtual void RenderView(ViewContext viewContext) {
            // TODO: Remove this hack. Without it, the browser appears to always load cached output
            viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
            ViewUserControlContainerPage containerPage = new ViewUserControlContainerPage(this);
            containerPage.RenderView(viewContext);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
           Justification = "There is already a ViewData property and it has a slightly different meaning.")]
        protected internal virtual void SetViewData(object viewData) {
            _viewData = viewData;
        }

        #region IViewDataContainer Members
        object IViewDataContainer.ViewData {
            get {
                EnsureViewData();
                return _viewData;
            }
        }
        #endregion

        private sealed class ViewUserControlContainerPage : ViewPage {
            public ViewUserControlContainerPage(ViewUserControl userControl) {
                Controls.Add(userControl);
            }
        }
    }
}

以直接在 Controller 中 RenderView 一个用户控件(ViewUserControl),asp.net mvc 会替我们创建了一个 "空白页" (ViewUserControlContainerPage )来装载这个控件RenderView(ViewUserControl) 有个限制,就是不能有 MasterPage。

    private sealed class ViewUserControlContainerPage : ViewPage {
           public ViewUserControlContainerPage(ViewUserControl userControl) {
               Controls.Add(userControl);
           }
       }

我们从 UrlRoutingModule 开始,历经 MvcRouteHandler、MvcHandler、Controller、ActionFilterAttribute,直到最后的 ViewEngine、ViewPage.完成了整个ASP.NET MVC的生命周期探索。

posted @ 2008-03-22 13:14 张善友 阅读(...) 评论(...) 编辑 收藏