使用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. 实际运行效果

浏览器断开的事件

相关推荐
2501_915373881 小时前
Vue 3零基础入门:从环境搭建到第一个组件
前端·javascript·vue.js
XYR1212124 小时前
C# 参数
c#
沙振宇4 小时前
【Web】使用Vue3开发鸿蒙的HelloWorld!
前端·华为·harmonyos
运维@小兵4 小时前
vue开发用户注册功能
前端·javascript·vue.js
蓝婷儿5 小时前
前端面试每日三题 - Day 30
前端·面试·职场和发展
Risehuxyc5 小时前
GrassRoot备份项目
c#
一口一个橘子5 小时前
[ctfshow web入门] web69
前端·web安全·网络安全
咩咩觉主5 小时前
c#数据结构 线性表篇 非常用线性集合总结
开发语言·数据结构·unity·c#·游戏引擎·程序框架
读心悦6 小时前
CSS:盒子阴影与渐变完全解析:从基础语法到创意应用
前端·css