一次请求,两次交互?用 ASP.NET 优雅处理用户数据补全

在最近的开发中,我遇到了这样一个需求:一次 HTTP 请求中,需要根据业务场景判断用户数据是否完整。如果数据不完整,逻辑流程需要暂停,提示用户补充必要信息。等用户补全数据后,系统再继续执行剩下的操作。

这个需求在传统的 ASP.NET 应用中并不常见,因为 HTTP 请求通常是无状态且一次性处理完毕的,中途“暂停再继续”的流程并不容易实现。接下来我将分享我是如何在 ASP.NET 中实现这个功能的。后端代码:

using Microsoft.AspNetCore.SignalR;using Microsoft.Extensions.Caching.Distributed;using System.Collections.Concurrent;
var builder = WebApplication.CreateBuilder(args);builder.Services.AddDistributedMemoryCache();builder.Services.AddSingleton<PendingRequestManager>();builder.Services.AddControllers();builder.Services.AddSignalR();builder.Services.AddCors(options =>{    options.AddDefaultPolicy(policy =>    {        policy.AllowAnyOrigin();    });});var app = builder.Build();app.UseCors();app.MapControllers();app.MapHub<NotifyHub>("/notifyHub");app.UseStaticFiles();app.MapPost("consume", async (string userId, PendingRequestManager manager, IDistributedCache cache, IHubContext<NotifyHub> hubContext, CancellationToken cancellationToken) =>{    var tcs = manager.Create(userId);    await hubContext.Clients.Group(userId).SendAsync("NeedSupplement", "服务器返回:请补充数据哦!!!!");    // 挂起请求,等待补充,最多300秒    var finished = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromSeconds(300)));    var message = await cache.GetStringAsync(userId, cancellationToken);    Console.WriteLine(message);    if (finished != tcs.Task)    {        manager.Remove(userId);        return Results.BadRequest("服务器返回:等待超时");    }    return Results.Ok("服务器返回:处理成功!");});app.MapPost("/supplement", async (string userId, DataMessage dataMessage, IDistributedCache cache, PendingRequestManager manager, CancellationToken cancellationToken) =>{    await cache.SetStringAsync(userId, dataMessage.Data, new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(2) }, cancellationToken);    // 实际上你应该验证数据并补充    var tcs = manager.Get(userId);    if (tcs != null)    {        tcs.SetResult(true);        manager.Remove(userId);    }    return Results.Ok("服务器返回:发送补充成功");});app.Run();
public class PendingRequestManager{    private ConcurrentDictionary<string, TaskCompletionSource<bool>> _pendingRequests = new();    public TaskCompletionSource<bool> Create(string userId)    {        var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);        _pendingRequests[userId] = tcs;        return tcs;    }    public TaskCompletionSource<bool>? Get(string userId)    {        _pendingRequests.TryGetValue(userId, out var tcs);        return tcs;    }    public void Remove(string userId)    {        _pendingRequests.TryRemove(userId, out _);    }}
public class NotifyHub : Hub{    public override Task OnConnectedAsync()    {             var userId = Context.GetHttpContext()?.Request.Query["userId"];        if (!string.IsNullOrEmpty(userId))        {                     Groups.AddToGroupAsync(Context.ConnectionId, userId);        }        return base.OnConnectedAsync();    }}public class DataMessage{    public string Data { get; set; }}

前端代码:

<!DOCTYPE html><html><head>    <meta charset="utf-8">    <title>SignalR补数据Demo</title>    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.5/signalr.min.js"></script></head><body>    <button id="consume">开始处理数据</button>    <br />    <textarea id="data" style="display:none;" rows="4" cols="50">我是浏览器上补的数据Data</textarea>    <br />    <button id="supplement" style="display:none;">开始补数据</button>    <div id="msg"></div>
    <script>        const userId = "user1";         let connection = new signalR.HubConnectionBuilder()            .withUrl("/notifyHub?userId=" + userId)            .build();
        connection.on("NeedSupplement", function (message) {            document.getElementById('msg').innerText = message;            document.getElementById('supplement').style.display = "";            document.getElementById('data').style.display = "";        });
        connection.start().then(function () {            console.log("SignalR Connected.");        }).catch(function (err) {            return console.error(err.toString());        });
        document.getElementById('consume').onclick = function() {            fetch('/consume?userId=' + userId, {                method: 'POST'            })            .then(resp => resp.json())            .then(data => {                document.getElementById('msg').innerText = JSON.stringify(data);            });        };
        document.getElementById('supplement').onclick = function() {            fetch('/supplement?userId=' + userId, {                method: 'POST',                headers: {                    'Content-Type': 'application/json'                },                body: JSON.stringify({                      data: document.getElementById('data').value,                })            })            .then(resp => resp.json())            .then(data => {                document.getElementById('msg').innerText = JSON.stringify(data);                document.getElementById('supplement').style.display = "none";                document.getElementById('data').style.display = "none";            });        };    </script></body></html>

TaskCompletionSource是什么?

  • TaskCompletionSource<T> 是 .NET 提供的一个可以手动控制 Task 何时完成的对象。
  • 用它你可以在某处 await 一个 Task,而在别的地方(甚至别的方法/线程)决定 Task 什么时候完成(SetResult/SetException/SetCanceled)。
  • 适用于异步等待外部事件/条件的场景。

声明:来自硅基-桂迹,仅代表创作者观点。链接:https://eyangzhen.com/1893.html

硅基-桂迹的头像硅基-桂迹

相关推荐

关注我们
关注我们
购买服务
购买服务
返回顶部