ActionResult 扩展不适用于 Page() ActionResult 方法

ActionResult extension doesn't work with Page() ActionResult method

我有一个 ActionResult 扩展,它在返回页面时向 TempData 添加了 toast:

public static IActionResult WithMessage(this ActionResult result, InformMessage msg)
{
    return new InformMessageResult(result, msg);
}

这是 InformMessageResult:

public class InformMessageResult : ActionResult
    {
        public ActionResult InnerResult { get; set; }
        public InformMessage InformMessage { get; set; }

        public InformMessageResult (ActionResult innerResult, InformMessage informMsg)
        {
            InnerResult = innerResult;
            InformMessage = informMsg;
        }

        public override async Task ExecuteResultAsync(ActionContext context)
        {
            ITempDataDictionaryFactory factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
            ITempDataDictionary tempData = factory.GetTempData(context.HttpContext);

            tempData.Put("InformMessage", InformMessage);

            await InnerResult.ExecuteResultAsync(context);
        }
    }

这适用于

return RedirectToPage(etc).WithMessage(etc)

等,但因

而失败
return Page().WithMessage(etc)

调试器突出显示

await InnerResult.ExecuteResultAsync(context);

表示 InnerResult 未设置为对象的实例。

有什么方法可以让 Return Page() 工作吗?

编辑以获取更多信息:

我测试了作为 "InnerResult" 发送的内容,它看起来像 Return Page(),一切都是空的(我想说是设计的,因为我什么也没做在那之前):

使用 RedirectToPage():

使用 Page():

我怀疑的问题是 Page() 和 RedirectToPage() 继承自不同的基 classes。

RedirectToPage() 根据此 documentation。它具有以下继承性:

Object -> ActionResult -> RedirectToPageResult

这是通过控制器的一些继承暴露出来的。因此,您可以使用 ActionResult 的扩展。

但是,根据此 documentation,Page() 方法是 RazorPages class 的一部分。所以它的继承方式如下:1

Object -> RazorPageBase -> PageBase -> Page

现在 class 的 Page() 方法执行 return 一个 PageResult 看起来从 ActionResult 继承为 defined here.

考虑到这一点,我建议先将其转换为基础 ActionResult,然后再使用您的扩展方法。可能是这样的:

var baseClass = (Page() as ActionResult);
return baseClass.WithMessage(etc);

1 您可以在 OP 提供的第二张图片中看到基本类型。

Page()RedirectToPage() 分别是 PageResultRedirectToPageResult 的辅助方法。因此,您可以调用这些方法而不是更新。

当您调用 Page() 时,它会在幕后调用 ExecuteResultAsync 来渲染 PageResult。那时,所有属性都为空,即 PageModelViewData 等。由于结果已经呈现,因此您不能调用另一个 ExecuteResultAsync 使用WithMessage 扩展方法。

当您调用 RedirectToPage 时,它会发出一个 301/302 并生成一个新的 Request,这将导致 RedirectToPageResult。因此 RedirectToPageResult 尚未呈现,您可以选择使用 WithMessage 扩展方法。

话虽如此,我认为在使用 Page() 方法时不可能使用 WithMessage,AFAIK。

这是一个较旧的问题,但我自己需要这样的功能,并深入挖掘以找到原因。

正如您从调试中看到的那样,Page 方法生成了一个完全空白的 PageResult。因为每个 属性 都是空的,调用 ExecuteResultAsync 失败,因为它显然不能对全空值做任何事情。

Page() 在其余时间正常工作的原因是 PageActionInvoker 中的幕后魔术,特别是在其 InvokeResultAsync 方法中。它会检测到您的 ViewData 和 Page 是空白的,并在它自己调用 pageResult.ExecuteResultAsync 方法之前填充它们。

因此,如果您执行与 PageActionInvoker 相同的工作,您仍然可以使 InformMessageResult 正常工作。方法如下:

public override async Task ExecuteResultAsync(ActionContext context)
{
    /* temp data work goes here */

    if (InnerResult is PageResult pageResult)
    {
        var pageContext = context as PageContext
            ?? throw new ArgumentException("context must be a PageContext if your InnerResult is a PageResult.", "context");

        var pageFactoryProvider = pageContext.HttpContext.RequestServices.GetRequiredService<IPageFactoryProvider>();
        var pageFactory = pageFactoryProvider.CreatePageFactory(pageContext.ActionDescriptor);

        var viewContext = new ViewContext(
            pageContext,
            NullView.Instance,
            pageContext.ViewData,
            tempData,
            TextWriter.Null,
            new Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelperOptions()
        );
        viewContext.ExecutingFilePath = pageContext.ActionDescriptor.RelativePath;

        pageResult.ViewData = viewContext.ViewData;
        pageResult.Page = (PageBase)pageFactory(pageContext, viewContext);
    }

    await InnerResult.ExecuteResultAsync(context);
}

private class NullView : IView
{
    public static readonly NullView Instance = new NullView();

    public string Path => string.Empty;

    public Task RenderAsync(ViewContext context)
    {
        if (context == null) throw new ArgumentNullException("context");
        return Task.CompletedTask;
    }
}