【问题标题】:ASP.NET Core Controlling HttpResponse WriteAsync Output LocationASP.NET Core 控制 HttpResponse WriteAsync 输出位置
【发布时间】:2019-08-02 16:41:56
【问题描述】:

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

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

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

【问题讨论】:

    标签: asp.net-core httpresponse flush


    【解决方案1】:

    在获得创意后,我找到了解决问题的方法,而不是完全按照我的要求做的解决方案。在我发现 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, "https://stackoverflow.com/questions/57330559/asp-net-core-controlling-httpresponse-flush-output-location"), 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);
            }
        }
    

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

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

    编辑:我确实发现在这个用例中实际上不需要“刷新”响应正文。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-14
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      • 2016-11-02
      相关资源
      最近更新 更多