ActionResult
原则上任何类型的响应都可以利用当前的HttpResponse来完成。但是MVC中我们一般将针对请求的响应实现在一个ActionResult对象中。
public abstract class ActionResult { protected ActionResult(); public abstract void ExecuteResult(ControllerContext context); }
ViewResult和ViewEngine
IViewEngine
viewResult通过ViewEngine实现对View的获取、激活和呈现。
public interface IViewEngine { ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache); ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache); void ReleaseView(ControllerContext controllerContext, IView view); }
现有两种实现:WebFormViewEngine(.aspx,.ascx),RazorViewEngine(.cshtml/vbhtml)
public void Index() { ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, "NonExistView", null); foreach (var item in result.SearchedLocations) { Response.Write(item + "<br/>"); } }
因为WebFormViewEngine排在RazorViewEngine之前,所以前者被优先使用。可以用 ViewEngines.Engines.RemoveAt(0);来移除对WebFormViewEngine的调用。
ViewResult
public class ViewResult : ViewResultBase { public string MasterName { get; set; } protected override ViewEngineResult FindView(ControllerContext context); }
ViewResultBase
public abstract class ViewResultBase: ActionResult { public object Model { get; } // 临时数据。 public TempDataDictionary TempData { get; set; } // 视图。 public IView View { get; set; } // 视图包 public dynamic ViewBag { get; } // 视图数据。 public ViewDataDictionary ViewData { get; set; } // 视图引擎的集合。 public ViewEngineCollection ViewEngineCollection { get; set; } // 视图的名称。 public string ViewName { get; set; } // 视图引擎。 protected abstract ViewEngineResult FindView(ControllerContext context); public override void ExecuteResult(ControllerContext context) { if (string.IsNullOrEmpty(this.ViewName)) { this.ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if(this.View == null) { result = this.FindView(context); this.View = result.View; } TextWriter output = context.HttpContext.Response.Output; ViewContext viewContext = new ViewContext(context, this.View, this.ViewData, this.TempData, output); this.View.Render(viewContext, output); if(result != null) { result.ViewEngine.ReleaseView(context, this.View); } } }
抽象类Controller中的几个重载方法
View的编译原理
Asp.net mvc默认情况下采用动态编译的方式对View文件实施编译。当我们在部署的时候,需要对.cshtml或.vbhtml文件进行打包。
针对某个文件的第一次访问会触发针对它的编译,一个View会被编译成一个具体的类型。View文件的每一次修改都会导致再一次编译。和.aspx页面一样的编译方式,默认是以目录为单位的,也就是同一个目录下的多个View被编译到同一个程序集中。
View程序集
public static class HtmlHelperExtensions { public static MvcHtmlString ListViewAssemblies(this HtmlHelper helper) { TagBuilder ul = new TagBuilder("ul"); foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("App_Web_"))) { TagBuilder li = new TagBuilder("li"); li.InnerHtml = assembly.FullName; ul.InnerHtml += li.ToString(); } return new MvcHtmlString(ul.ToString()); } }
View:
<div>当前View类型:@this.GetType().AssemblyQualifiedName</div> <div>当前加载的View程序集:</div> @Html.ListViewAssemblies()
页面展示:
BuildManager
Response.Write(BuildManager.GetCompiledType("~/Views/Bar/Action1.cshtml") + "<br/>");
Response.Write(BuildManager.GetCompiledType("~/Views/Bar/Action2.cshtml") + "<br/>");
Response.Write(BuildManager.TargetFramework.FullName+ "<br/>");
Response.Write(BuildManager.GetCompiledCustomString("~/Views/Foo/Action1.cshtml") + "<br/>");
Response.Write(BuildManager.GetCompiledAssembly("~/Views/Foo/Action1.cshtml").Location + "<br/>");
页面展示:
WebViewPage的继承树
由.cshtml文件编译后产生的类文件
namespace ASP { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Web; using System.Web.Helpers; using System.Web.Security; using System.Web.UI; using System.Web.WebPages; using System.Web.Mvc; using System.Web.Mvc.Ajax; using System.Web.Mvc.Html; using System.Web.Optimization; using System.Web.Routing; using MvcApp; public class _Page_Views_Foo_Action1_cshtml : System.Web.Mvc.WebViewPage<dynamic> { protected ASP.global_asax ApplicationInstance { get { return ((ASP.global_asax)(Context.ApplicationInstance)); } } public override void Execute() { BeginContext("~/Views/Foo/Action1.cshtml", 0, 14, true); WriteLiteral("<div>当前View类型:"); EndContext("~/Views/Foo/Action1.cshtml", 0, 14, true); BeginContext("~/Views/Foo/Action1.cshtml", 15, 36, false); Write(this.GetType().AssemblyQualifiedName); EndContext("~/Views/Foo/Action1.cshtml", 15, 36, false); BeginContext("~/Views/Foo/Action1.cshtml", 51, 34, true); WriteLiteral("</div> <div>当前加载的View程序集:</div> "); EndContext("~/Views/Foo/Action1.cshtml", 51, 34, true); BeginContext("~/Views/Foo/Action1.cshtml", 86, 25, false); Write(Html.ListViewAssemblies()); EndContext("~/Views/Foo/Action1.cshtml", 86, 25, false); BeginContext("~/Views/Foo/Action1.cshtml", 111, 2, true); WriteLiteral(" "); EndContext("~/Views/Foo/Action1.cshtml", 111, 2, true); } } }
View编译后的生成的类型是WebViewPage<TModel>的子类,WebViewPage<TModel>是WebViewPage的子类,泛型TModel是View的Model类型。
WebPageExecutingBase
public abstract class WebPageExecutingBase { public virtual dynamic App { get; } public virtual HttpApplicationStateBase AppState { get; } public virtual HttpContextBase Context { get; set; } public virtual string VirtualPath { get; set; } public virtual IVirtualPathFactory VirtualPathFactory { get; set; } public abstract void Execute(); public virtual string Href(string path, params object[] pathParts); public virtual string NormalizePath(string path); protected internal virtual TextWriter GetOutputWriter(); protected internal virtual string NormalizeLayoutPagePath(string layoutPagePath); //动态内容 public abstract void Write(object value); public abstract void Write(HelperResult result); public static void WriteTo(TextWriter writer, object content); public static void WriteTo(TextWriter writer, HelperResult content); //静态内容 public abstract void WriteLiteral(object value); public static void WriteLiteralTo(TextWriter writer, object content); public virtual void WriteAttribute(string name, PositionTagged<string> prefix, PositionTagged<string> suffix, params AttributeValue[] values); public virtual void WriteAttributeTo(TextWriter writer, string name, PositionTagged<string> prefix, PositionTagged<string> suffix, params AttributeValue[] values); protected internal virtual void WriteAttributeTo(string pageVirtualPath, TextWriter writer, string name, PositionTagged<string> prefix, PositionTagged<string> suffix, params AttributeValue[] values); protected internal void BeginContext(int startPosition, int length, bool isLiteral); protected internal void BeginContext(string virtualPath, int startPosition, int length, bool isLiteral); protected internal void BeginContext(TextWriter writer, int startPosition, int length, bool isLiteral); protected internal void BeginContext(TextWriter writer, string virtualPath, int startPosition, int length, bool isLiteral); protected internal void EndContext(int startPosition, int length, bool isLiteral); protected internal void EndContext(string virtualPath, int startPosition, int length, bool isLiteral); protected internal void EndContext(TextWriter writer, int startPosition, int length, bool isLiteral); protected internal void EndContext(TextWriter writer, string virtualPath, int startPosition, int length, bool isLiteral); }
WebPageRenderingBase
public abstract class WebPageRenderingBase : WebPageExecutingBase, ITemplateFile { public virtual Cache Cache { get; } public string Culture { get; set; } public virtual bool IsAjax { get; } public virtual bool IsPost { get; } public abstract string Layout { get; set; } // An object that contains page data. public abstract dynamic Page { get; } public abstract IDictionary<object, dynamic> PageData { get; } public WebPageContext PageContext { get; } public ProfileBase Profile { get; } public virtual HttpRequestBase Request { get; } public virtual HttpResponseBase Response { get; } public virtual HttpServerUtilityBase Server { get; } public virtual HttpSessionStateBase Session { get; } public virtual TemplateFileInfo TemplateInfo { get; } public string UICulture { get; set; } public virtual IList<string> UrlData { get; } public virtual IPrincipal User { get; internal set; } protected internal IDisplayMode DisplayMode { get; } public abstract void ExecutePageHierarchy(); public abstract HelperResult RenderPage(string path, params object[] data); }
ExecutePageHierarchy负责整个页面内容的输出,
View自身内容通过重写Execute方法来输出。
WebPageBase
public abstract class WebPageBase : WebPageRenderingBase { //其他成员…… // Gets or sets the path of a layout page. public override string Layout { get; set; } public TextWriter Output { get; } // Returns the text writer instance that is used to render the page. protected internal override TextWriter GetOutputWriter(); // Gets the stack of System.IO.TextWriter objects for the current page context. public Stack<TextWriter> OutputStack { get; } // Returns and removes the context from the top of the System.Web.WebPages.WebPageBase.OutputStack // instance. public void PopContext(); // Inserts the specified context at the top of the System.Web.WebPages.WebPageBase.OutputStack // instance. public void PushContext(WebPageContext pageContext, TextWriter writer); // Executes the code in a set of dependent web pages. public override void ExecutePageHierarchy(); // Executes the code in a set of dependent web pages by using the specified parameters. public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer); // Executes the code in a set of dependent web pages by using the specified context, // writer, and start page. public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage); // Returns a value that indicates whether the specified section is defined in the // page. public bool IsSectionDefined(string name); // Called by content pages to create named content sections. public void DefineSection(string name, SectionWriter action); // In layout pages, renders the portion of a content page that is not within a named // section. public HelperResult RenderBody(); // Renders the content of one page within another page. public override HelperResult RenderPage(string path, params object[] data); // In layout pages, renders the content of a named section. public HelperResult RenderSection(string name); // In layout pages, renders the content of a named section and specifies whether // the section is required. public HelperResult RenderSection(string name, bool required); // Writes the specified object as an HTML-encoded string. public override void Write(object value); // Writes the specified System.Web.WebPages.HelperResult object as an HTML-encoded // string. public override void Write(HelperResult result); // Writes the specified object without HTML-encoding it first. public override void WriteLiteral(object value); }
WebPageBase 实现了抽象方法ExecutePageHierarchy,定义了额外的两个ExecutePageHierarchy方法重载,参数startPage表示定义在“_ViewStart.cshtml”文件的开始页面。
最初呈现的内容来源于3个部分:布局文件,开始页面和View自身的内容。完整页面内容的呈现通过调用ExecutePageHierarchy方法来完成。
WebPageBase 实现了抽象方法Write/WriteLiteral和抽象属性 Layout。
定义了在布局文件上输出Section的方法。
WebViewPage
public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild { // The System.Web.Mvc.AjaxHelper object that is used to render HTML using Ajax. public AjaxHelper<object> Ajax { get; set; } // The System.Web.HttpContext object that is associated with the page. public override HttpContextBase Context { get; set; } // The System.Web.Mvc.HtmlHelper object that is used to render HTML elements. public HtmlHelper<object> Html { get; set; } public object Model { get; } public TempDataDictionary TempData { get; } public UrlHelper Url { get; set; } public dynamic ViewBag { get; } public ViewContext ViewContext { get; set; } public ViewDataDictionary ViewData { get; set; } // Runs the page hierarchy for the ASP.NET Razor execution pipeline. public override void ExecutePageHierarchy(); // Initializes the System.Web.Mvc.AjaxHelper, System.Web.Mvc.HtmlHelper, // and System.Web.Mvc.UrlHelper classes. public virtual void InitHelpers(); // Sets the view context and view data for the page. protected override void ConfigurePage(WebPageBase parentPage); protected virtual void SetViewData(ViewDataDictionary viewData); }
WebViewPage<TModel>
public abstract class WebViewPage<TModel> : WebViewPage { public AjaxHelper<TModel> Ajax { get; set; } public HtmlHelper<TModel> Html { get; set; } public TModel Model { get; } public ViewDataDictionary<TModel> ViewData { get; set; } public override void InitHelpers(); protected override void SetViewData(ViewDataDictionary viewData); }
提供针对泛型参数TModel的属性Ajax, Html, Model, ViewData, 而初始化它们的InitHelpers和SetViewData方法也被重写
View的呈现
IView
对应View引擎来说,View通过IView来表示
public interface IView { void Render(ViewContext viewContext, TextWriter writer); }
View的呈现体现在对WebViewPage对象的激活上。
在Asp.net mvc 中Razor引擎下的View通过RazorView对象来表示,WebForm引擎的View则通过WebFormView对象表示,二者都继承自BuildManagerCompiledView。
RazorView 实现
BuildManagerCompiledView
public abstract class BuildManagerCompiledView : IView { internal IViewPageActivator ViewPageActivator; private IBuildManager _buildManager; private ControllerContext _controllerContext; internal IBuildManager BuildManager { get { if (this._buildManager == null) { this._buildManager = new BuildManagerWrapper(); } return this._buildManager; } set { this._buildManager = value; } } public string ViewPath { get; protected set; } protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath) : this(controllerContext, viewPath, null) { } protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator) : this(controllerContext, viewPath, viewPageActivator, null) { } internal BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator, IDependencyResolver dependencyResolver) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (string.IsNullOrEmpty(viewPath)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewPath"); } this._controllerContext = controllerContext; this.ViewPath = viewPath; this.ViewPageActivator = (viewPageActivator ?? new BuildManagerViewEngine.DefaultViewPageActivator(dependencyResolver)); } public virtual void Render(ViewContext viewContext, TextWriter writer) { if (viewContext == null) { throw new ArgumentNullException("viewContext"); } object obj = null; Type compiledType = this.BuildManager.GetCompiledType(this.ViewPath); if (compiledType != null) { obj = this.ViewPageActivator.Create(this._controllerContext, compiledType); } if (obj == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_ViewCouldNotBeCreated, new object[] { this.ViewPath })); } this.RenderView(viewContext, writer, obj); } protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance); }
RazorView
/// <summary>Represents the class used to create views that have Razor syntax.</summary> public class RazorView : BuildManagerCompiledView { public string LayoutPath { get; private set; } public bool RunViewStartPages { get; private set; } internal StartPageLookupDelegate StartPageLookup { get; set; } internal IVirtualPathFactory VirtualPathFactory { get; set; } internal DisplayModeProvider DisplayModeProvider { get; set; } public IEnumerable<string> ViewStartFileExtensions { get; private set; } public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions) : this(controllerContext, viewPath, layoutPath, runViewStartPages, viewStartFileExtensions, null) { } /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.RazorView" /> class using the view page activator.</summary> /// <param name="controllerContext">The controller context.</param> /// <param name="viewPath">The view path.</param> /// <param name="layoutPath">The layout or master page.</param> /// <param name="runViewStartPages">A value that indicates whether view start files should be executed before the view.</param> /// <param name="viewStartFileExtensions">The set of extensions that will be used when looking up view start files.</param> /// <param name="viewPageActivator">The view page activator.</param> public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions, IViewPageActivator viewPageActivator) : base(controllerContext, viewPath, viewPageActivator) { this.LayoutPath = (layoutPath ?? string.Empty); this.RunViewStartPages = runViewStartPages; this.StartPageLookup = new StartPageLookupDelegate(StartPage.GetStartPage); this.ViewStartFileExtensions = (viewStartFileExtensions ?? Enumerable.Empty<string>()); } /// <summary>Renders the specified view context by using the specified writer and <see cref="T:System.Web.Mvc.WebViewPage" /> instance.</summary> /// <param name="viewContext">The view context.</param> /// <param name="writer">The writer that is used to render the view to the response.</param> /// <param name="instance">The <see cref="T:System.Web.Mvc.WebViewPage" /> instance.</param> protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance) { if (writer == null) { throw new ArgumentNullException("writer"); } WebViewPage webViewPage = instance as WebViewPage; if (webViewPage == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, new object[] { base.ViewPath })); } webViewPage.OverridenLayoutPath = this.LayoutPath; webViewPage.set_VirtualPath(base.ViewPath); webViewPage.ViewContext = viewContext; webViewPage.ViewData = viewContext.ViewData; webViewPage.InitHelpers(); if (this.VirtualPathFactory != null) { webViewPage.set_VirtualPathFactory(this.VirtualPathFactory); } if (this.DisplayModeProvider != null) { webViewPage.set_DisplayModeProvider(this.DisplayModeProvider); } WebPageRenderingBase webPageRenderingBase = null; if (this.RunViewStartPages) { webPageRenderingBase = this.StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, this.ViewStartFileExtensions); } webViewPage.ExecutePageHierarchy(new WebPageContext(viewContext.HttpContext, null, null), writer, webPageRenderingBase); } }
以IoC的方式激活View
RazorView 的构造函数中有用到IViewPageActivator,IViewPageActivator旨在View对象的激活。当没有指定一个具体的ViewPageActivator时,默认采用 DefaultViewPageActivator对象。
internal class DefaultViewPageActivator : IViewPageActivator { private Func<IDependencyResolver> _resolverThunk; public DefaultViewPageActivator() : this(null) { } public DefaultViewPageActivator(IDependencyResolver resolver) { if (resolver == null) { this._resolverThunk = (() => DependencyResolver.Current); return; } this._resolverThunk = (() => resolver); } public object Create(ControllerContext controllerContext, Type type) { object result; try { result = (this._resolverThunk().GetService(type) ?? Activator.CreateInstance(type)); } catch (MissingMethodException originalException) { MissingMethodException ex = TypeHelpers.EnsureDebuggableException(originalException, type.FullName); if (ex != null) { throw ex; } throw; } return result; } }
扩展
扩展WebViewPage
IDependencyResolver 是Asp.net mvc提供的IoC接入口,所以这里可以介入到具体某个WebViewPage<TModel>类型的实例化。
下面的例子是向View中注入一个属性,用于在页面中便捷地读取资源文件信息。
public abstract class LocalizableViewPage<TModel> : WebViewPage<TModel> { [Inject] public ResourceReader ResourceReader { get; set; } }
public class DefaultResourceReader : ResourceReader { public override string GetString(string name) { return Resources.ResourceManager.GetString(name); } }
在View 顶部 添加命令 @inherits LocalizableViewPage<>
@inherits LocalizableViewPage<object> <html> <head> <title></title> </head> <body> <h2>@ResourceReader.GetString("HelloWorld")</h2> </body> </html>
或者修改View文件夹下的Web.config配置
<system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="LocalizableViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Optimization"/> <add namespace="System.Web.Routing" /> <add namespace="MvcApp" /> </namespaces> </pages> </system.web.webPages.razor>
扩展RazorViewEngine
public class MyViewEngine : RazorViewEngine { public MyViewEngine() { ViewLocationFormats = new[] { "~/Views/{1}/{0}.cshtml", "~/Views/Shared/{0}.cshtml", "~/Views/Areas/{1}/{0}.cshtml" }; AreaViewLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Config/{1}/{0}.cshtml", "~/Areas/{2}/Views/User/{1}/{0}.cshtml", "~/Areas/{2}/Views/Case/{1}/{0}.cshtml", "~/Areas/{2}/Views/Config/Shared/{0}.cshtml", "~/Areas/{2}/Views/User/Shared/{0}.cshtml", "~/Areas/{2}/Views/Case/Shared/{0}.cshtml" }; } public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { return base.FindView(controllerContext, viewName, masterName, useCache); } } ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new MyViewEngine());