.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文件。

.NET Core开发日志——视图与页面

使用反编译软件,比如dotPeek,查看里面的内容,会找到一些由cshtml文件生成的类。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxfxp.html