在 ASP.NET Core 用 Response.Body.FlushAsync 實現簡易即時進度回報
0 |
今年初分享過一個用 Response.Flush 在 ASP.NET MVC 實現即時進度回報的極簡風做法,但 ASP.NET Core 網站架構不同己無法適用。不管在什麼平台,極簡風永遠是我的最愛,所以,ASP.NET Core 版的簡易進度回報寫法來了!
ASP.NET Core 移除了 Reponse.Flush() 方法,最接近的替代方案是 Response.Body.FlushAsync(),配合 Response.Body.WriteAsync(),因此,WebForm / ASP.NET MVC 時代的 Reponse.Write() + Response.Flush() 概念稍加修改便能在 ASP.NET Core 重現。
繼續用昨天的 scanimage 當範例。
假設我想從 ASP.NET Core 呼叫 scanimage,透過 BeginErrorReadLine() 從 StandardError 接收進度回報並即時顯示在網頁上,讓使用者能看到 15.1%, 36.6%, 57.7%... 的數字跳動,正規做法可以考慮用 SignalR、WebSocket、Server-Event實現即時資訊更新,但在小型網站或 Coding4Fun 專案,我更喜歡寫幾行程式就把它搞定。
先看執行結果:(註:為了方便在 Windows 測試,我寫了一個 scanimage 模擬器,故進度為 0.5 秒固定增加 5%)
接著來看程式碼。呼叫 scanimage 的部分我包成 ScanService 了,Scan() 方法要傳入一個 Action<string> Callback 函式接收 stderr 傳回的掃描進度資訊。由於進度數字要刷新在同一行,我寫了兩個 JavaScript 函式 printMessage()、updateProgress() 分別處理添加一行新訊息或更新進度訊息,在接收到 scanimage 回傳文字後,透過 Reponse.Body.WriteAsync() 輸出 <script>printMessage(...)或updateProgress(...)</script>,再呼叫 Reponse.Body.FlushAsync() 立即送出,但這裡有個眉角,即使呼叫了 FlushAsync(),Kestrel 會等累積到 1024 Bytes 後才真的送出,類似行為之前 IE 遇過,這次用同樣手法解決 - 將輸出內容補空白到 1024 個字元。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using M225dwScan.Models;
namespace M225dwScan.Controllers
{
public class ScanController : Controller
{
private readonly ILogger<ScanController> _logger;
private readonly ScanService scanSvc;
public ScanController(ILogger<ScanController> logger, ScanService scanSvc)
{
_logger = logger;
this.scanSvc = scanSvc;
}
public async Task Index(string mode, string resolution, string source,
string format, int top = 0, int left = 0, int width = 100, int height = 100)
{
var body = Response.Body;
Action<string, bool> print = async (msg, padding) => {
if (padding) msg = msg.PadRight(1024, ' ');
var data = System.Text.Encoding.UTF8.GetBytes(msg);
await body.WriteAsync(data, 0, data.Length);
await body.FlushAsync();
};
print(@"
<html>
<head>
<style>html,body{font-size:9pt;}</style>
</head>
<body></body>
<script>
function updateProgress(msg) {
var p = document.getElementById('progress');
if (!p) {
p = document.createElement('div');
p.setAttribute('id','progress');
document.body.appendChild(p);
}
p.innerText = msg;
}
function printMessage(msg) {
var m = document.createElement('div');
m.innerText = msg;
document.body.appendChild(m);
}
</script>", false);
Func<object, string> toJson = o => System.Text.Json.JsonSerializer.Serialize(o);
var img = await scanSvc.Scan(mode, resolution, source, format, top, left, width, height,
(msg) => //回報進度 Callback,msg 為 scanimage stderr 顯示內容
{
if (msg.StartsWith('\r')) //以 \r 起首時為進度數字
print($"<script>updateProgress({toJson(msg.TrimStart('\r'))});</script>", true);
else
print($"<script>printMessage({toJson(msg)});</script>", true);
});
print("</html>", false);
}
}
}
雖稱不上嚴謹有效率,但簡單有效,短短幾行搞定,在想快速實現即時回報的場合不失為一個好選擇。
Example of using Response.Body.FlushAsync() to report progress realtime in ASP.NET Core.
Comments
Be the first to post a comment