簡體   English   中英

ASP.NET Core Controller 返回的 ViewResult 在哪里消費?

[英]Where is the ViewResult returned by ASP.NET Core Controller consumed?

在 ASP.NET Core Controller的定義中,它將View()方法定義為返回一個ViewResult對象

namespace Microsoft.AspNetCore.Mvc
{
     //...
    public abstract class Controller : ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable
    {
     //...   
        public virtual ViewResult View();
     //...
    }
}

它在框架中的哪個位置調用了如下所示的控制器方法並使用了調用View()方法返回的ViewResult

public class HomeController : Controller
{
  public IActionResult Index()
    {
        return View();
    }
}

整個過程相當ViewExecutor ,由ResourceInvoker內部管理,但負責處理ViewResult的主要管道是ViewExecutor源代碼)。

ViewExecutor的主要入口點是ExecuteAsync()方法,它接受ActionContext和您的ViewResult 然后它使用注冊的IViewEngine (通常是RazorViewEngine )來定位相應的視圖,以識別相應的視圖並返回一個ViewEngineResult實例 然后使用該ViewEngineResult執行和呈現視圖。

擴展ViewExecutor

如果需要自定義邏輯,可以通過實現IActionResultExecutor<>接口,然后使用 ASP.NET Core 的依賴注入容器將其注冊為單例來實現自己的視圖執行器類。 這樣做時,我建議參考開箱即用的ViewResultExecutor的源代碼

如果您想實現用於定位視圖的自定義邏輯,這會很有用。 例如,您可能想要考慮狀態或上下文數據,例如路由數據、請求標頭、cookie、會話等。 定位視圖時。

因此,作為一個非常簡單的示例,假設您希望允許使用查詢字符串值(例如?View=MyView )有選擇地選擇?View=MyView 在這種情況下,您可以創建一個QueryStringViewResultExecutor 這是一個基本的概念驗證:

public class QueryStringViewResultExecutor : ViewExecutor, IActionResultExecutor<ViewResult> 
{
    public QueryStringViewResultExecutor(
        IOptions<MvcViewOptions> viewOptions,
        IHttpResponseStreamWriterFactory writerFactory,
        ICompositeViewEngine viewEngine,
        ITempDataDictionaryFactory tempDataFactory,
        DiagnosticListener diagnosticListener,
        IModelMetadataProvider modelMetadataProvider
    ) : base(
        viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticListener, modelMetadataProvider
    ) 
    {
    }

    public async Task ExecuteAsync(ActionContext context, ViewResult result) 
    {
        var viewEngineResult = FindView(context, result); // See helper method below
        viewEngineResult.EnsureSuccessful(originalLocations: null);
        var view = viewEngineResult.View;

        using (view as IDisposable) 
        {
            await ExecuteAsync(
                context,
                view,
                result.ViewData,
                result.TempData,
                result.ViewName,
                result.StatusCode
            ).ConfigureAwait(false);
        }
    }

    private ViewEngineResult FindView(ActionContext actionContext, ViewResult viewResult) 
    {

        // Define variables
        var view = (ViewEngineResult?)null;
        var viewEngine = viewResult.ViewEngine?? ViewEngine;
        var searchedPaths = new List<string>();
        var requestContext = actionContext.HttpContext.Request;

        // If defined, attempt to locate view based on query string variable
        if (requestContext.Query.ContainsKey("View")) 
        {
            var queryStringValue = requestContext.Query["View"].First<string>();
            if (queryStringValue is not null) 
            {
                view = viewEngine.FindView(actionContext, queryStringValue, isMainPage: true);
                searchedPaths = searchedPaths.Union(view.SearchedLocations?? Array.Empty<string>()).ToList();
            }
        }

        // If no view is found, fall back to the view defined on the viewResult
        if (!view?.Success?? true) 
        {
            view = viewEngine.FindView(actionContext, viewResult.ViewName, isMainPage: true);
            searchedPaths = searchedPaths.Union(view.SearchedLocations ?? Array.Empty<string>()).ToList();
        }

        // Return view for processing by the razor engine
        if (view is not null and { Success: true }) {
            return view;
        }
        return ViewEngineResult.NotFound(viewResult.ViewName, searchedPaths);

    }
}

然后,您可以在Startup.ConfigureServices()方法中將其注冊為:

services.Services.TryAddSingleton<IActionResultExecutor<ViewResult>, QueryStringViewResultExecutor>();

免責聲明:您可能需要注銷開箱即用的ViewExecutor以避免沖突。 但是,通常情況下,您使用自定義ViewResult注冊IActionResultExecutor<> ,因此,這不是必需的; 見下文。

擴展ViewResult

通常,您希望將其與自定義ViewResult配對。 為什么這很有用? 通常是因為您需要從ControllerViewResultExecutor傳遞額外的數據。

因此,作為一個人為的示例,假設您有主題,並且可以根據該主題選擇性地自定義視圖。 然后,您可以將Theme屬性添加到您的ViewResult ,從而允許ViewResultExecutor首先查找基於主題的視圖,否則回退到非主題版本。

public class ThemedViewResult : ViewResult 
{

    public string Theme { get; set; }

    public override async Task ExecuteResultAsync(ActionContext context) 
    {
        var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<ThemedViewResult>>();
        await executor.ExecuteAsync(context, this).ConfigureAwait(false);
    }
}

即使您不需要自定義ViewResult ,也值得暫停一下以評估此代碼。 底層ViewResult類實現IActionResult ,它公開了一個方法ExecuteResultAsync() 這是由頂部提到的ResourceInvoker類調用的。 這依次定位已注冊的IActionResultExecutor<>即我們在上一節中創建和注冊的組件類型——並調用其ExecuteAsync()方法。

注意:這是一個人為的示例,因為通常其他上下文數據可以訪問主題,例如RouteData 、自定義ClaimsPrincipalISession實現。 但是您可以想象在其他時候,這些信息將是特定於請求的,並且最好通過ViewResultController中繼。 例如,也許是一個 CMS,可以在每頁的基礎上選擇主題。

由於不清楚您是否真的需要擴展此功能,或者只是好奇所有內容如何組合在一起,因此上述示例僅作為概念驗證; 他們沒有經過測試。 盡管如此,它們還是應該讓您基本了解ViewResult的處理位置和方式,以及在您需要時擴展該行為的選項。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM