首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一次请求,两次交互?用 ASP.NET 优雅处理用户数据补全

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

作者头像
郑子铭
发布2025-09-02 17:31:05
发布2025-09-02 17:31:05
7600
代码可运行
举报
运行总次数:0
代码可运行

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

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

后端代码:

代码语言:javascript
代码运行次数:0
运行
复制
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; }
}

前端代码:

代码语言:javascript
代码运行次数:0
运行
复制
<!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)。
  • 适用于异步等待外部事件/条件的场景。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档