ASP.NET 核心控制 HttpResponse WriteAsync 输出位置

ASP.NET Core Controlling HttpResponse WriteAsync Output Location

是否可以从 .NET Core 控制器对 HttpResponse 执行 WriteAsync,但控制后续 WriteAsync 调用将写入呈现内容的何处?前任。 Flush 1 发送完整的 HTML 文档(可能是布局视图),但所有后续 WriteAsync 调用都呈现在 HTML 主体中而不是附加到末尾。

我有一个涉及一系列重定向的过程,这个问题与该过程中的一站式有关。不幸的是,这一站的一些步骤相当长运行,我想在将它们发送到下一站之前向客户提供步骤反馈。但是,我希望它看起来不错(不仅仅是页面上的文本流),因此能够控制它在文档中呈现的位置会很不错。

我知道还有其他方法可以做到这一点(SignalR 等)但是因为这只是重定向系列的一站,所以我想保持请求打开直到我准备好重定向到下一站并且是希望我能让解决方案非常简单。

在发挥创意之后,我找到了解决问题的方法,而不是完全按照我的要求进行操作的解决方案。在我发现 JavaScript <script> 块仍然会被浏览器处理,即使它们被插入到结束 </html> 标记之后的 DOM 中,我意识到我可以利用立即函数在我希望它们出现在正文中的位置添加元素。我的解决方案执行以下操作:

  1. MVC 控制器将完整的 HTML 页面写入响应并 将其刷新回客户端
  2. 随着后续步骤的处理,控制器写入 响应的脚本块并刷新它们
    • 这些脚本块是直接函数,可操纵 DOM 组件以在实际终止请求之前添加我想要显示的步骤

示例

控制器.cs文件:

    [Route("example")]
    public class ExampleController : Controller
    {
        private readonly ILogger<ExampleController> _logger;
        private readonly IExampleControllerBL _controllerBl;
        private const string htmlPage = @"
<!DOCTYPE HTML>
<html>
    <head>
        <title>Learning Management - Course Auto-Enrollment</title>
        <style>
            body {
                font-size: 1.15em;
                color: #333;
                font-family: Arial, Helvetica;
                height:100%;
            }
            .stepRow {
                display: flex;
                width: 100% ;
            }
            .stepCell, {
                display: flex;
                height: 2rem;
                align-items: center;
            }
            #output {
                min-height: 350px;
                padding-bottom: 15px 0;
            }
            #footer {
                background-color: #ccc;
                padding: 20px;
                margin: 0;
            }
        </style>
        <script>        
            function addStep(stepText)
            {
                var output = document.querySelector('#output');                
                var stepRow = document.createElement('div');
                var stepCell = document.createElement('div'); 
                stepRow.className = 'stepRow';
                stepCell.className = 'stepCell';
                stepRow.appendChild(stepCell);
                output.appendChild(stepRow);
                stepCell.innerHTML = stepText;  
            }
        </script>
    </head>
    <body>
        <h1>Flushed Response Example</h1>
        <p>Example of flushing output back to the client and then redirecting when complete.</p>
        <h3>Progress:</h3>
        <div id=""output""></div>
        <div id=""footer"">Footer here</div>
    </body>
</html>
";

        private const string stepTemplate = @"
<script>
    (function(){{
        addStep('&bull; {0}');
    }}());
</script>
";

        private const string redirectTemplate = @"
<script>
    (function(){{
        window.location.replace('{0}');
    }}());
</script>
";

        public ExampleController(ILogger<ExampleController> logger, IExampleControllerBL controllerBl)
        {
            _logger = logger;
            _controllerBl = controllerBl;
        }

        [HttpGet]
        [Route("steps"), HttpGet]
        public async Task SteppedResponseExample(CancellationToken cancellationToken)
        {
            //Write full HTML page back to response
            Response.Clear();
            await Response.WriteAsync(htmlPage, cancellationToken);

            //Execute business logic and pass step change handler
            await _controllerBl.ExecuteMultiStepProcess(async (step) =>
                    {
                        //Write step script block back to response
                        await Response.WriteAsync(string.Format(stepTemplate, step), cancellationToken);
                    });

            //Write redirect script block to response
            await Response.WriteAsync(string.Format(redirectTemplate, ""), cancellationToken);
        }
    }

业务逻辑.cs文件:

    public class ExampleControllerBL : IExampleControllerBL
    {
        public async Task ExecuteMultiStepProcess(Func<string, Task> OnStartStep)
        {
            await OnStartStep("Performing step 1...");
            await Task.Delay(2500);
            await OnStartStep("Performing step 2...");
            await Task.Delay(2500);
            await OnStartStep("Performing step 3...");
            await Task.Delay(2500);
            await OnStartStep("Performing step 4...");
            await Task.Delay(2500);
            await OnStartStep("Performing step 5...");
            await Task.Delay(2500);
            await OnStartStep("Performing step 6...");
            await Task.Delay(2500);
        }
    }

一旦您开始刷新响应,尝试 return 一个 IAction 结果对象是没有意义的,因为 MVC 中间件管道将抛出错误,因为状态代码已通过刷新设置等。这就是我的端点没有 return 任何东西的原因。

显然需要考虑错误处理,因为它也必须通过刷新来处理,除非您只想让最终用户对它为何停止感到困惑。但是,这是让最终用户了解涉及重定向和处理的过程的好方法。希望这对其他人有用。

编辑:我确实发现在这个用例中实际上不需要 "Flush" 响应主体。