使用C# ASP.NET创建一个可以由服务端推送信息至客户端的WEB应用(2)

接上文

使用C# ASP.NET创建一个可以由服务端推送信息至客户端的WEB应用(1)
https://blog.csdn.net/coldwind811201/article/details/147607641

1. 更新NuGet包

升级NuGet包后,注意相应修改前面页面上的JS引用为相应新版本的jquery JS脚本

2. 对前台页面进行AJAX改造

2.1 前台页面控件

将aspx上原 asp:Button 改为普通HTML的button

增加停止处理的button 最开始隐藏这个按钮 只有在点下开始处理后 显示这个停止处理的按钮 在停止后继续隐藏该按钮

html 复制代码
<asp:Button ID="btnProcess" runat="server" Text="开始处理" />

<button id="btnProcess" type="button">开始处理</button>
<button id="btnStop" type="button" style="display: none;">停止处理</button>

修改显示信息的部分 由DIV块变更为textarea组件

html 复制代码
<textarea id="txtLog" rows="10"></textarea>

如果不进行AJAX改造,按下停止处理按钮会触发页面整体刷新,完法实现直接在当前页面上修改按钮状态,在日志栏里显示停止信息,用户交互很不友好

2.2 前台JS脚本部分

增加 connectionId 用于区分客户端

javascript 复制代码
    <script type="text/javascript">
        $(function () {
            // 获取 SignalR hub 的代理
            var processHub = $.connection.processHub;
            var connectionId = "";

            // 启动连接
            $.connection.hub.start().done(function () {
                connectionId = $.connection.hub.id;
                console.log('SignalR 连接已建立');
            }).fail(function (error) {
                console.log('SignalR 连接失败: ' + error);
            });
        });
    </script>

增加绑定按钮JS代码 两个按钮 增加在启动连接 成功建立连接后 开始处理(#btnProcess) 停止处理 (#btnStop)

javascript 复制代码
// 为 开始处理 按钮添加点击事件
$('#btnProcess').click(function () {
    // 禁用按钮防止重复点击,并修改按钮上的文字
    $(this).prop('disabled', true).text('处理中...');

    // 显示停止处理按钮允许用户停止处理
    //document.getElementById("btnStop").style.display = "inline-block";  // 或者使用 "block" 取决于你的布局需求
    $("#btnStop").prop('disabled', false);
    $("#btnStop").show();

    // 重置进度条
    //$('#progressBar').css('width', '0%').attr('aria-valuenow', 0);
    //$('#progressMessage').text('开始处理数据...');

    // 发送Ajax请求
    $.ajax({
        url: '<%= ResolveUrl("~/Default.aspx/StartProcessing") %>',
        type: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        data: JSON.stringify({ connectionId: connectionId }),
        success: function (response) {
            console.log('已启动处理');
        },
        error: function (xhr, status, error) {
            console.error('启动失败:', error);
            // 启用开始处理按钮
            $('#btnProcess').prop('disabled', false).text('开始处理');
            // 隐藏停止处理按钮
            //document.getElementById("btnStop").style.display = "none";
            $("#btnStop").hide();  // 如果你使用jQuery库,代码会更简洁

            //$('#progressMessage').text('启动处理失败: ' + error);
        }
    });
});
javascript 复制代码
// 为 停止处理 按钮 添加点击事件
$('#btnStop').click(function () {
    // 禁用按钮防止重复点击,并修改按钮上的文字
    $(this).prop('disabled', true).text('停止中...');

    // 发送Ajax请求
    $.ajax({
        url: '<%= ResolveUrl("~/Default.aspx/CancelProcessing") %>',
        type: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        data: JSON.stringify({ connectionId: connectionId }),
        success: function (response) {
            console.log('已启动停止');
        },
        error: function (xhr, status, error) {
            console.error('启动停止失败:', error);
        }
    });
});

增加2个由后台调用的前台JS 消息处理 以及任务完成的处理

javascript 复制代码
// 定义客户端接收进度更新的方法 由后台程序调用
processHub.client.updateProgress = function (message) {
    var $textarea = $('#txtLog');
    $textarea.val(function (i, currentValue) {
        return currentValue + message + '\n';
    });
    $textarea.scrollTop($textarea[0].scrollHeight);  // 滚动到最底部
};

// 定义处理完成的回调函数 由后台程序调用
processHub.client.processingComplete = function () {
    // 启用开始处理按钮
    $('#btnProcess').prop('disabled', false).text('开始处理');
    // 隐藏停止处理按钮
    $("#btnStop").text('停止处理');
    $("#btnStop").hide();
};

3. 对后台程序进行改造

3.1 修改ProcessHub 类

  1. 支持多个客户端连接
  2. 每个客户端可以停止处理过程
  3. 如果客户端直接关闭浏览器,服务端也相应停止执行
csharp 复制代码
public class ProcessHub : Hub
{
    // 此 Hub 将用于向客户端发送消息
    public static IHubContext HubContext
    {
        get { return GlobalHost.ConnectionManager.GetHubContext<ProcessHub>(); }
    }

    // 使用静态字典来存储每个连接的CancellationTokenSource
    public static readonly ConcurrentDictionary<string, CancellationTokenSource> _connectionTokens = new ConcurrentDictionary<string, CancellationTokenSource>();


    // 获取或创建特定连接的CancellationTokenSource
    public static CancellationTokenSource GetCancellationTokenSource(string connectionId)
    {
        return _connectionTokens.GetOrAdd(connectionId, id => new CancellationTokenSource());
    }


    // 移除并取消特定连接的CancellationTokenSource
    public static void RemoveCancellationTokenSource(string connectionId)
    {
        if (_connectionTokens.TryRemove(connectionId, out var cts))
        {
            cts.Cancel();
            cts.Dispose();
        }
    }

    // 客户端直接关闭的情况的处理
    public override Task OnDisconnected(bool stopCalled)
    {
        var connectionId = Context.ConnectionId;
        Debug.WriteLine($"{connectionId} Disconnected!");

        // 只取消当前断开连接的TokenSource
        if (_connectionTokens.TryRemove(connectionId, out var cts))
        {
            cts.Cancel();
            cts.Dispose();
        }

        return base.OnDisconnected(stopCalled);
    }

}

3.2 修改Default.aspx后台代码

由前端按钮AJAX触发

  1. 线程化处理
  2. 停止处理功能的实现 由前台按钮AJAX触发
csharp 复制代码
// 由HTML的BUTTON AJAX触发
[WebMethod]
public static void StartProcessing(string connectionId)
{
    // 获取或创建该连接的CancellationTokenSource
    var cts = ProcessHub.GetCancellationTokenSource(connectionId);

    // 如果之前已请求取消,则重置
    if (cts.IsCancellationRequested)
    {
        ProcessHub.RemoveCancellationTokenSource(connectionId);
        cts = ProcessHub.GetCancellationTokenSource(connectionId);
    }

    // 创建一个任务来异步处理数据
    Task.Run(() => ProcessData(connectionId, cts.Token));
}

private static void ProcessData(string connectionId, CancellationToken cancellationToken)
{
    // 模拟初始延迟
    //Thread.Sleep(100);
    ProcessHub.HubContext.Clients.Client(connectionId).updateProgress($"开始处理...");  // updateProgress是前台脚本

    // 发送进度更新到客户端 处理在这里进行
    try
    {
        for (int i = 0; i < 100; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();

            ProcessHub.HubContext.Clients.Client(connectionId).updateProgress($"正在处理 {i} 的数据...");
            Thread.Sleep(100);  // 模拟处理需要0.	1秒
        }
    }
    catch (OperationCanceledException)  // 被用户中止
    {
        ProcessHub.HubContext.Clients.Client(connectionId).updateProgress("用户中止!");
        ProcessHub.HubContext.Clients.Client(connectionId).processingComplete();    // processingComplete是前台脚本
        return;
    }
    finally
    {
        // 处理完成后清理资源
        ProcessHub.RemoveCancellationTokenSource(connectionId);
    }

    // 正常完成
    ProcessHub.HubContext.Clients.Client(connectionId).updateProgress("数据处理完成!");
    ProcessHub.HubContext.Clients.Client(connectionId).processingComplete();
}


// 由HTML的BUTTON AJAX触发
[WebMethod]
public static void CancelProcessing(string connectionId)
{
    ProcessHub.HubContext.Clients.Client(connectionId).updateProgress($"正在停止...");  // updateProgress是前台脚本

    // 只取消特定连接的TokenSource
    if (ProcessHub._connectionTokens.TryGetValue(connectionId, out var cts))
    {
        cts.Cancel();
    }
}

4. 实际运行效果

浏览器断开的事件

相关推荐
辻戋21 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保21 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun1 天前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp1 天前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.1 天前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl1 天前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫1 天前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友1 天前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理1 天前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻1 天前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js