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)开始他的执行流程
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing" />
</httpModules>
代码如下:
{
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 管线会依次处理下面的请求:
-
对请求进行验证,将检查浏览器发送的信息,并确定其是否包含潜在恶意标记。
-
如果已在 Web.config 文件的 UrlMappingsSection 节中配置了任何 URL,则执行 URL 映射。
-
引发 BeginRequest 事件。
-
引发 AuthenticateRequest 事件。
-
引发 PostAuthenticateRequest 事件。
-
引发 AuthorizeRequest 事件。
-
引发 PostAuthorizeRequest 事件。
-
引发 ResolveRequestCache 事件。
-
引发 PostResolveRequestCache 事件。
-
根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理。如果该请求针对从 Page 类派生的对象(页),并且需要对该页进行编译,则 ASP.NET 会在创建该页的实例之前对其进行编译。
-
引发 PostMapRequestHandler 事件。
-
引发 AcquireRequestState 事件。
-
引发 PostAcquireRequestState 事件。
-
引发 PreRequestHandlerExecute 事件。
-
为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 BeginProcessRequest)。例如,如果该请求针对某页,则当前的页实例将处理该请求。
-
引发 PostRequestHandlerExecute 事件。
-
引发 ReleaseRequestState 事件。
-
引发 PostReleaseRequestState 事件。
-
如果定义了 Filter 属性,则执行响应筛选。
-
引发 UpdateRequestCache 事件。
-
引发 PostUpdateRequestCache 事件。
-
引发 EndRequest 事件。
上述UrlRoutingModule订阅了两个 HttpApplication 事件,PostResolveRequestCache 要比 PostMapRequestHandler 更早执行,先看PostResolveRequestCache 事件,他首先从首先从 RouteCollection 中获取一个 RouteData 对象。看看RouteCollection 属性:
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
set
{
this._routeCollection = value;
}
}
看到了来自RouteTable,这不正是在Global.asax.cs 中添加的 Route 集合:
{
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:
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:
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
}
}
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。
get {
if (_controllerBuilder == null) {
_controllerBuilder = ControllerBuilder.Current;
}
return _controllerBuilder;
}
set {
_controllerBuilder = value;
}
}
如果我们自定义MvcHandler,则需要好好的看看ControllerBuilder.Current:
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。
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:
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<string, object> _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<string, object> ViewData {
get {
if (_viewData == null) {
_viewData = new Dictionary<string, object>();
}
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。
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方式的执行:
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:
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。
// 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() 来完成这个工作。
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 Windsor 、StructureMap 、Spring.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的生命周期探索。
欢迎大家扫描下面二维码成为我的客户,扶你上云