1. 程式人生 > >.NET Core開發日誌——視圖與頁面

.NET Core開發日誌——視圖與頁面

代碼 核心 tel await got 實現類 name world! 對象

當一個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接口的實現。從RazorPageBaseRazorPage,再到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開發日誌——視圖與頁面