.NET Core开发日志——视图与页面
当一个action完成它的任务后,通常需要返回一个实现iactionresult的对象,而最常见的就是view或者viewresult,所谓的视图对象。那么视图与最终所看到的页面之间的联系又是怎样形成的,这便是本文想要探讨的问题。
在resourceinvoker类之中,可以找到下列的代码。这些代码是对返回结果——iactionresult的进一步处理。
case state.resultinside: { ... var task = invokeresultasync(_result); if (task.status != taskstatus.rantocompletion) { next = state.resultend; return task; } goto case state.resultend; } protected async task invokeresultasync(iactionresult result) { var actioncontext = _actioncontext; _diagnosticsource.beforeactionresult(actioncontext, result); _logger.beforeexecutingactionresult(result); try { await result.executeresultasync(actioncontext); } finally { _diagnosticsource.afteractionresult(actioncontext, result); _logger.afterexecutingactionresult(result); } }
iactionresult接口的实现类viewresult中会调用viewresultexecutor类的方法。
public override async task executeresultasync(actioncontext context) { ... var executor = context.httpcontext.requestservices.getrequiredservice<iactionresultexecutor<viewresult>>(); await executor.executeasync(context, this); }
viewresultexecutor类里则需要先通过razorviewengine类找到对应的视图。
public async task executeasync(actioncontext context, viewresult result) { ... var viewengineresult = findview(context, result); viewengineresult.ensuresuccessful(originallocations: null); var view = viewengineresult.view; using (view as idisposable) { await executeasync( context, view, result.viewdata, result.tempdata, result.contenttype, result.statuscode); } ... }
razorviewengine类返回的结果是razorview对象。注意其内部已包含了irazorpage对象。
public viewengineresult getview(string executingfilepath, string viewpath, bool ismainpage) { ... var cacheresult = locatepagefrompath(executingfilepath, viewpath, ismainpage); return createviewengineresult(cacheresult, viewpath); } public viewengineresult findview(actioncontext context, string viewname, bool ismainpage) { ... var cacheresult = locatepagefromviewlocations(context, viewname, ismainpage); return createviewengineresult(cacheresult, viewname); } private viewengineresult createviewengineresult(viewlocationcacheresult result, string viewname) { ... var page = result.viewentry.pagefactory(); var viewstarts = new irazorpage[result.viewstartentries.count]; for (var i = 0; i < viewstarts.length; i++) { var viewstartitem = result.viewstartentries[i]; viewstarts[i] = viewstartitem.pagefactory(); } var view = new razorview(this, _pageactivator, viewstarts, page, _htmlencoder, _diagnosticsource); return viewengineresult.found(viewname, view); }
找到视图后,viewresultexecutor再调用其父类viewexecutor的executeasync方法。其内部将调用razorview类的renderasync方法。
protected async task executeasync( viewcontext viewcontext, string contenttype, int? statuscode) { ... var response = viewcontext.httpcontext.response; responsecontenttypehelper.resolvecontenttypeandencoding( contenttype, response.contenttype, defaultcontenttype, out var resolvedcontenttype, out var resolvedcontenttypeencoding); response.contenttype = resolvedcontenttype; if (statuscode != null) { response.statuscode = statuscode.value; } using (var writer = writerfactory.createwriter(response.body, resolvedcontenttypeencoding)) { var view = viewcontext.view; var oldwriter = viewcontext.writer; try { viewcontext.writer = writer; diagnosticsource.beforeview(view, viewcontext); await view.renderasync(viewcontext); diagnosticsource.afterview(view, viewcontext); } finally { viewcontext.writer = oldwriter; } // perf: invoke flushasync to ensure any buffered content is asynchronously written to the underlying // response asynchronously. in the absence of this line, the buffer gets synchronously written to the // response as part of the dispose which has a perf impact. await writer.flushasync(); } }
razorview类中可以看到其核心的处理与irazorpage的executeasync方法紧密相关。
public virtual async task renderasync(viewcontext context) { ... _bufferscope = context.httpcontext.requestservices.getrequiredservice<iviewbufferscope>(); var bodywriter = await renderpageasync(razorpage, context, invokeviewstarts: true); await renderlayoutasync(context, bodywriter); } private async task<viewbuffertextwriter> renderpageasync( irazorpage page, viewcontext context, bool invokeviewstarts) { var writer = context.writer as viewbuffertextwriter; ... // the writer for the body is passed through the viewcontext, allowing things like htmlhelpers // and viewcomponents to reference it. var oldwriter = context.writer; var oldfilepath = context.executingfilepath; context.writer = writer; context.executingfilepath = page.path; try { if (invokeviewstarts) { // execute view starts using the same context + writer as the page to render. await renderviewstartsasync(context); } await renderpagecoreasync(page, context); return writer; } finally { context.writer = oldwriter; context.executingfilepath = oldfilepath; } } private async task renderpagecoreasync(irazorpage page, viewcontext context) { page.viewcontext = context; _pageactivator.activate(page, context); _diagnosticsource.beforeviewpage(page, context); try { await page.executeasync(); } finally { _diagnosticsource.afterviewpage(page, context); } }
但当查找irazorpage接口的实现。从razorpagebase
到razorpage
,再到razorpage<tmodel>
,这些都只是抽象类,且都没有对executeasync方法有具体实现。
源码里找不到进一步的实现类,线索到这里断开了。
这时可以建立一个mvc的应用程序,编译后找到它的bin目录,会看到其中包含一个*.view.dll文件。
使用反编译软件,比如dotpeek,查看里面的内容,会找到一些由cshtml文件生成的类。
以其中views_home_index为例,其实际上为razorpage<tmodel>
的一个实现类。
它内部的executeasync方法正是生成页面内容的关键。
因为是vs模板自动生成的页面,上面的代码十分冗杂。为了更清晰地检查核心的代码,不妨减少下页面的复杂度。
把index.cshtml文件内容改成如下:
@{ viewdata["title"] = "home page"; layout = null; } <p>hello world!</p>
再次编译后,可以看到executeasync方法的内容变成了下面的样子:
public virtual async task executeasync() { ((viewdatadictionary) this.get_viewdata()).set_item("title", (object) "home page"); ((razorpagebase) this).set_layout((string) null); ((razorpagebase) this).begincontext(65, 21, true); ((razorpagebase) this).writeliteral("\r\n<p>hello world!</p>"); ((razorpagebase) this).endcontext(); }
不难看出,最终展现的页面内容便是通过razorpagebase类的writeliteral方法生成的。
推荐阅读
-
.NET Core开发日志——Middleware
-
.NET Core开发日志——OData
-
.NET Core开发日志——ADO.NET与SQL Server
-
[Asp.Net Core] Blazor Server Side 开发教程 - 安装环境与运行第一个程序
-
循序渐进学.Net Core Web Api开发系列【0】:序言与目录
-
.NET Core开发日志——简述路由
-
.NET Core开发日志——HttpContext
-
循序渐进学.Net Core Web Api开发系列【10】:使用日志
-
.NET Core开发日志——Linux版本的SQL Server
-
.Net Core开发日志——Global Tools