【问题标题】:Where is the ViewResult returned by ASP.NET Core Controller consumed?ASP.NET Core Controller 返回的 ViewResult 消耗在哪里?
【发布时间】:2021-09-26 14:43:09
【问题描述】:

在 ASP.NET Core Controller class 的定义中,它将View() method 定义为返回一个ViewResult object

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();
    }
}

【问题讨论】:

  • 这是因为您感兴趣,还是您尝试做的事情不起作用?

标签: c# .net asp.net-core asp.net-core-mvc


【解决方案1】:

整个过程非常复杂,由ResourceInvoker class 内部管理,但负责处理ViewResult 的主要管道是ViewExecutor class (source code)。

ViewExecutor 的主要入口点是ExecuteAsync() method,它接受ActionContext 和您的ViewResult。然后它使用注册的IViewEngine(通常是RazorViewEngine)定位适当的视图以识别相应的视图并返回ViewEngineResult instance。然后使用 ViewEngineResult 执行和渲染视图。

扩展ViewExecutor

如果您需要自定义逻辑,您可以通过实现IActionResultExecutor<> interface 来实现自己的视图执行器类,然后使用 ASP.NET Core 的依赖注入容器将其注册为单例。这样做时,我建议参考the out-of-the-box ViewResultExecutor's source code

如果您想实现自定义逻辑来定位视图,这会很有用。例如,您可能想要考虑状态或上下文数据,例如路由数据、请求标头、cookie、会话等。定位视图时。

因此,作为一个非常简单的示例,假设您希望允许使用查询字符串值(例如,?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&lt;&gt;,因此,这不是必需的;见下文。

扩展ViewResult

通常,您会希望将此与自定义 ViewResult 配对。为什么这很有用?通常是因为您需要从您的Controller 向您的ViewResultExecutor 传递额外的数据。

因此,作为一个人为的示例,假设您有主题,并且可以选择基于该主题自定义视图。然后,您可以将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&lt;&gt;——即我们在上一节中创建和注册的组件类型——并调用它的ExecuteAsync()方法。

注意:这是一个人为的示例,因为其他上下文数据通常可以访问主题,例如RouteData、自定义ClaimsPrincipalISession 实现。但是您可以想象其他时候,这些信息将是特定于请求的,最好通过ViewResultController 中继。例如,也许是一个 CMS,其中可以按页面选择主题。

由于不清楚您是否真的需要扩展此功能,或者只是好奇所有内容如何组合在一起,所以上述示例仅作为概念验证;他们没有经过测试。不过,它们应该让您大致了解 ViewResult 的处理位置和方式,以及在您需要时扩展该行为的选项。

【讨论】:

  • 我已经扩展了我对ViewResultIActionResult的实现的解释,它是从ResourceInvoker调用的,负责在@上调用ExecuteAsync()方法987654380@。这有助于澄清这三个组件之间的关系,以及它们如何相互作用。否则这会令人困惑,因为ViewExecutor 不是直接从ResourceInvoker 调用的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
相关资源
最近更新 更多