宋庆江 发表于 2023-8-22 16:41:38

SignalR实战:在.NET Framework和.NET Core中如何使用SignalR?

官网文档:https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-6.0&tabs=visual-studio
SignalR开源代码:https://github.com/signalr

很多小伙伴问:在前后端分离项目中,后端是.NET Core前端是Vue如何使用SignalR?在前后端不分离项目中,.NET Framework MVC项目中又如何使用SignalR技术呢?那就来看看下面这篇文章吧!本文主要介绍SignalR在实际项目中的应用,以及.NET Framework和.NET Core中如何去使用SignalR。

一、SignalR介绍

1.1-SignalR介绍

ASP.NET Core SignalR是一个开放源代码库,可用于简化向应用添加实时Web功能,实时Web功能使服务器端代码能够将内容推送到客户端。

1.2-SignalR的应用


[*]需要从服务器进行高频更新的应用:包括游戏、社交网络、投票、拍卖、地图和GPS引用。
[*]仪表盘和监视应用:包括公司仪表板、即时销售更新或旅行报警
[*]协作应用:包括白板应用和团队会议软件。
[*]通知应用:社交网络、电子邮件、聊天、游戏、旅行报警和其他应用都需要使用的通知。

二、.NET Framework使用SignalR

参考文献:
Owin介绍:https://www.cnblogs.com/Pinapple/p/6721361.html
Owin相关案例:https://blog.csdn.net/bifan546/article/details/77098896/

2.1-服务端(.NET Framework MVC)

(1)选择要使用Signalr的项目,点击【管理NuGet程序包】。

(2)搜索【SignalR】包,找到这个后然后点击下载。

会自动安装四个,注意他们之间有依赖关系:

(3).NET Framework平台需要添加Owin相关的包。
OWIN 是一种定义 Web 服务器和应用程序组件之间的交互的规范 。这一规范的目的是发展一个广阔且充满活力的、基于 Microsoft .NET Framework 的 Web 服务器和应用程序组件生态系统。
Microsoft.Owin.Hosting
Microsoft.Owin.Cors
Microsoft.Owin.Host.HttpListener
(4)在Web项目(要使用的Signal)中创建一个【Startup.cs】文件。
using JiCai.Admin.Hubs;
using Microsoft.Owin;
using Owin;


namespace JiCai.Admin
{
    public class Startup
    {
      /// <summary>
      /// 应用程序配置
      /// </summary>
      /// <param name="app"></param>
      public void Configuration(IAppBuilder app)
      {
            //启用SignalR
            app.MapSignalR();
            //绑定多个Hub
            app.MapSignalR<DemoHub>("/demoHub");
      }
    }
}例如:

(5)可以创建一个【Hubs】文件夹,专门放置Hub相关文件。[西瓜程序猿]这边是创建一个Hub文件,名为【DemoHub.cs】。
using Fenqibao.DTO;
using JiCai.Admin.Hubs.ConectionOperate;
using JiCai.Admin.Hubs.Models;
using log4net;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace JiCai.Admin.Hubs
{
    /// <summary>
    /// 报表管理-总表Hub
    /// </summary>
    public class SummaryTableHub :PersistentConnection
    {
      public readonly BaseService _base = new BaseService();
      private readonly ILog logger = LogManager.GetLogger(typeof(SummaryTableHub));
      private ConnectionManagement summaryTableCon = new ConnectionManagement();
      public CookieUserData LoginUserData
      {
            get
            {
                IOperator oper = ContainerManager.Resolve<IOperator>();
                LoginUser loginUser = oper as LoginUser;
                if (loginUser != null && loginUser.UserData != null)
                {
                  return loginUser.UserData;
                }
                return null;
            }
      }

      /// <summary>
      /// 连接成功后调用
      /// </summary>
      /// <param name="request"></param>
      /// <param name="connectionId"></param>
      /// <returns></returns>
      protected override Task OnConnected(IRequest request, string connectionId)
      {
            //获得SignalR的连接id
            var connid = connectionId;
            //获得用户id
            var userid = LoginUserData.Id.ToString();
            Console.Write($"【{connid}】:已建立连接!");

            //判断一下用户是不是已经链接了
            var checkUserConn = summaryTableCon.IsConn(connid, userid);
            if (!checkUserConn)
            {
                //添加一个新的连接
                summaryTableCon.AddConnInfo(new SignalRConn()
                {
                  UserId = userid,
                  ConnectionId = connid
                });
            }
            //更新连接
            else
            {
                summaryTableCon.UpdateConnInfo(userid, connid);
            }

            return Connection.Send(connectionId, $"【用户:{connectionId}】真正连接成功!");
            //return base.OnConnected(request, connectionId);
      }

      /// <summary>
      /// 接收到请求的时候调用
      /// </summary>
      /// <param name="request"></param>
      /// <param name="connectionId"></param>
      /// <param name="data"></param>
      /// <returns></returns>
      protected override async Task OnReceived(IRequest request, string connectionId, string data)
      {
            //获得用户id
            var userid = LoginUserData.Id.ToString();
            await Task.Factory.StartNew(async () =>
            {
                while (true)
                {
                                        var list = GetSummaryTableList(userid);
                  string json_jieshou_mes = "";
                  if (list != null && list.Count > 0)
                  {
                                                json_jieshou_mes = JsonConvert.SerializeObject(list);
                  }


                  await Connection.Send(connectionId, json_jieshou_mes);

                  //每5秒同步一次
                  await Task.Delay(5000);
                }
            }, TaskCreationOptions.LongRunning);

            //return base.OnReceived(request, connectionId, data);
      }

      /// <summary>
      /// 连接中断的时候调用
      /// </summary>
      /// <param name="request"></param>
      /// <param name="connectionId"></param>
      /// <param name="stopCalled"></param>
      /// <returns></returns>
      protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
      {
            Console.Write($"【{connectionId}】:已断开连接!");
            //获得SignalR的连接id
            var connid = connectionId;
            //关闭连接
            summaryTableCon.DelConnInfo(connid);
            return base.OnDisconnected(request, connectionId, stopCalled);
      }

      /// <summary>
      /// 连接超时重新连接的时候调用
      /// </summary>
      /// <param name="request"></param>
      /// <param name="connectionId"></param>
      /// <returns></returns>
      protected override Task OnReconnected(IRequest request, string connectionId)
      {
            return base.OnReconnected(request, connectionId);
      }


                /// <summary>
                /// 查询数据
                /// </summary>
                /// <param name="userId"></param>
                /// <returns></returns>
                private List<SummaryTableDataModel> GetSummaryTableList(string userId)
      {
            var result = _base.Query<SummaryTableDataModel>($@"
                          select * from demo-data
                                        ;
            ").ToList();
                        return result;
      }
    }
}(6)在Hubs/ConectionOperate文件夹中,[西瓜程序猿]这边创建【ConnectionManagement】文件,用来管理所有连接。
using System.Collections.Generic;
using System.Linq;

namespace JiCai.Admin.Hubs.ConectionOperate
{
    /// <summary>
    /// 连接管理
    /// </summary>
    public class ConnectionManagement
    {
      /// <summary>
      /// 用户连接集合
      /// </summary>
      public staticList<SignalRConn> SignalRConns { get; set; } = new List<SignalRConn>();

      /// <summary>
      /// 添加连接
      /// </summary>
      /// <param name="conn"></param>
      publicvoid AddConnInfo(SignalRConn conn)
      {
            SignalRConns.Add(conn);
      }

      /// <summary>
      /// 删除连接
      /// </summary>
      /// <param name="connid"></param>
      publicvoid DelConnInfo(string connid)
      {
            var signalRConns = SignalRConns.FirstOrDefault(u => u.ConnectionId == connid);
            if (signalRConns != null)
            {
                SignalRConns.Remove(signalRConns);
            }
      }

      /// <summary>
      /// 更新链接(老的链接不起作用了)
      /// 场景:客户端重连了,userid没变,但是connid变了
      /// </summary>
      /// <param name="userId">用户id</param>
      /// <param name="newConnsId">新的链接id</param>
      publicvoid UpdateConnInfo(string userId, string newConnsId)
      {
            var signalRConns = SignalRConns.FirstOrDefault(u => u.UserId.ToLower() == userId.ToLower());
            if (signalRConns != null)
            {
                signalRConns.ConnectionId = newConnsId;
            }
      }

      /// <summary>
      /// 判断用户是否已经链接
      /// </summary>
      /// <param name="connid">连接id</param>
      /// <param name="userid">用户id</param>
      /// <returns></returns>
      public bool IsConn(string connid,string userid)
      {
            var userConn = SignalRConns.FirstOrDefault(u => u.ConnectionId.ToLower() == connid.ToLower() && u.UserId.ToLower() == userid.ToLower());
            return userConn == null ? false : true;
      }
    }
}(7)在Hubs/ConectionOperate文件夹中,创建【SignalRConn.cs】文件用来作为SignalR和系统用户的连接实体。
namespace JiCai.Admin.Hubs.ConectionOperate
{
    /// <summary>
    /// 连接
    /// </summary>
    public class SignalRConn
    {
      /// <summary>
      /// 系统用户id
      /// </summary>
      public string UserId { get; set; }

      /// <summary>
      /// SignleR链接Id(每次链接SignalR都会分配一个id)
      /// </summary>
      public string ConnectionId { get; set; }
    }
}
2.2-客户端(JS)

(1)下载相关jq/signalr相关包,分别是【jquery.signalR-2.4.3.js】和【jquery-1.6.4.min.js】。可以访问下载(如果失效了,请联系我[西瓜程序猿])。
下载地址(编码:yRLCRp81):https://yongteng.lanzoub.com/iXDlu1631ugd
密码:44x5
文件截图:


(2)创建一个js文件【data_list_table_hub.js】,用来连接signalR。
// 连接服务
var connection = $.connection("/summary_table_hub");

// 建立链接
connection.start(function () {
    //连接成功
    console.log("西瓜程序猿-【" + new Date().toLocaleString() +"】连接成功!");
    //发送消息
    connection.send("给我数据吧");
});

// 连接断开
connection.disconnected(function () {
    console.log("西瓜程序猿-【" + new Date().toLocaleString() +"】连接断开!");
});

// 接收服务器发来的消息
connection.received(function (data) {
    console.log("西瓜程序猿-【" + new Date().toLocaleString() +"】接收服务器发来的消息:");
    console.log(data);
    //显示数据
    if (data != "" && checkJson(data)) {
      var obj = JSON.parse(data);
      var html_box = "";
      for (var i = 0; i < obj.length; i++) {
            html_box += `<tr>
            <td>`+obj.project_name+`</td>
            <td>`+ obj.zuori_sum+`</td>
            <td>`+ obj.jinri_sum+`</td>
            <td>`+ obj.qunian_sum+`</td>
            <td>`+ obj.jinnian_sum+`</td>
            <td>`+ obj.sum+`</td>
            <td>`+ obj.yikaipiao_sum+`</td>
            <td>`+ obj.weikaipiao_sum +`</td>
            <td>`+ obj.yishoupiao_sum +`</td>
            <td>`+ obj.weishoupiao_sum +`</td>
            <td>`+ obj.kehu_yinghuikuan_sum+`</td>
            <td>`+ obj.kehu_yihuikuan_sum+`</td>
            <td>`+ obj.kehu_weihuikuan_sum +`</td>
            <td>`+ obj.fuwu_yingfukuan_sum+`</td>
            <td>`+ obj.fuwu_yifukuan_sum+`</td>
            <td>`+ obj.fuwu_weifukuan_sum+`</td>
            </tr>`
      }
      $("#last_async_date").text(new Date().toLocaleString());
      $("#table_body").html(html_box);
    }
});

//判断是不是json字符串
function checkJson(str) {
    if (typeof str == 'string') {
      try {
            var obj = JSON.parse(str);
            // 等于这个条件说明就是JSON字符串 会返回true
            if (typeof obj == 'object' && obj) {
                return true;
            } else {
                //不是就返回false
                return false;
            }
      } catch (e) {
            return false;
      }
    }
    return false;
}(3)创建要展示的文件,我这边是用MVC模式,所有前台文件是cshtml。
@{
    ViewBag.Title = "西瓜程序猿-明细表";
}

@section styles{
   
}

@section scripts{
   
   
   
}


   
   
   
      
            报表最新同步时间:
      
      
            <table >
                <colgroup>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                  <col>
                </colgroup>
                <thead>
                  <tr>
                        <th colspan="2" >项目信息</th>
                        <th colspan="5" >销售情况</th>
                        <th colspan="6" >款项情况</th>
                  </tr>
                  <tr>
                        <th >项目名称</th>
                        <th >服务商名称</th>
                        <th >昨天订单总额</th>
                        <th >今天订单总额</th>
                        <th >去年订单总额</th>
                        <th >今年订单总额</th>
                        <th >订单总额</th>

                        <th >客户应回款总额</th>
                        <th >客户已回款总额</th>
                        <th >客户未回款总额</th>
                        <th >服务商应付款总额</th>
                        <th >服务商已付款总额</th>
                        <th >服务商未付款总额</th>
                  </tr>
                </thead>
                <tbody id="table_body">
                </tbody>
            </table>
      
    (4)效果展示:


三、.NET Core WebAPI使用SignalR

场景:项目中需要服务端主动向客户端发送通知消息,后端是使用.NETCore实现的,前端是使用Vue3全家桶项目,前后端分离模式。本次主要讲使用SignalR如何来实现的。

3.1-服务端(.NET Core WebAPI)

1、右击项目,点击【管理NuGet程序包】安装SignalR。

2、搜索【SignalR】,点击【安装】。


3、如果是.NET6以前的版本,在【Startup.cs】中配置:

如果是.NET6以后得版本,在【Program.cs】配置:
var builder = WebApplication.CreateBuilder(args);
//添加SignalR服务
builder.Services.AddSignalR();


app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
      name: "default",
      pattern: "{controller=Home}/{action=Index}/{id?}"
    );

    //添加SignalR端点
    endpoints.MapHub<ServerMonitorHub>("/serverMonitorHub");
});
4、创建SignalR中心
中心是一个类,用于处理客户端服务器通信的高级管道。在SignalR_Demo项目文件夹中,创建Hubs文件夹。在Hubs文件夹中,使用已下代码创建ChatHub类。
    public class ChatHub:Hub
    {
      /// <summary>
      /// 发送消息
      /// </summary>
      /// <param name="user">用户名</param>
      /// <param name="message">发送信息</param>
      /// <returns></returns>
      public async Task SendMessage(string user,string message)
      {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
      }
    }
3.2-客户端(Vue3+Vite+TS)

(1)安装SugbalR
npm install @latelier/vue-signalr版本截图:

(2)然后新建一个文件,用来封装业务逻辑相关代码。[西瓜程序猿]是在【src/utils】目录下,创建了一个名为【signalr.ts】的文件,也可以是js文件,根据自己项目的需要去新建。

代码:
import * as signalRfrom '@microsoft/signalr';

//如果需要身份验证
//.withUrl('/messageHub', {accessTokenFactory: () => sessionStorage.getItem('token')})
let connection;

// 建立连接
async function start(url) {
try {
    connection = new signalR.HubConnectionBuilder()
      .withUrl(url)//跨域需要使用绝对地址
      .configureLogging(signalR.LogLevel.Information)
      .withAutomaticReconnect() // 设置自动重连机制
      .build();
} catch(err) {
    console.log(err);
    setTimeout(start, 10000);//错误重连
}
}

// 开始signalr连接
const connect = async (url) => {
await start(url);
console.log(`【西瓜程序猿-${new Date().toLocaleString()}:SignalR已连接成功!`);
};

// 调用服务端方法
async function send(methodName, param){
try {
    await connection.invoke(methodName, param);
} catch (err) {
    console.error(err);
}
}

//断开连接
const disconnect = async ()=>{
await connection.stop();
console.log(`【西瓜程序猿-${new Date().toLocaleString()}:SignalR已断开连接!`);
};

export {
connection,
connect,
send,
disconnect
};(3)然后再页面进行使用。[西瓜程序猿]这里以Echarts图表展示为例子,首先先安装Echarts包。
npm install echarts --save
版本截图:

(4)然后再页面写入如下代码,具体页面样式根据需要进行修改调整哦。
<template>

<breadcrumb/>


    <el-row>

      <el-col :span="24" >
      <el-card>
          <template #header>
            <i ></i> 服务器信息
          </template>
         
            <table cellspacing="0" >
            <tbody>
                <tr>
                  <td >主机名称</td>
                  <td >{{serverInfo.HostName}}</td>
                  <td >系统名称</td>
                  <td >{{serverInfo.OSDescription}}</td>
                </tr>
                <tr>
                  <td >IP地址</td>
                  <td >{{serverInfo.IpAddress}}</td>
                  <td >操作系统</td>
                  <td >{{serverInfo.OperatingSystem}} {{serverInfo.OsArchitecture}}</td>
                </tr>
            </tbody>
            </table>
         
      </el-card>
      </el-col>


      <el-col :span="24" >
      <el-card>
          <template #header>
            <i ></i> 应用信息
          </template>
         
            <table cellspacing="0" >
            <tbody>
                <tr>
                  <td >.NET Core版本</td>
                  <td >{{serverInfo.FrameworkDescription}}</td>
                  <td >内存占用</td>
                  <td >{{serverInfo.MemoryFootprint}}</td>
                </tr>
                <tr>
                  <td >环境变量</td>
                  <td >{{serverInfo.EnvironmentName}}</td>
                  <td >项目路径</td>
                  <td >{{serverInfo.ContentRootPath}}</td>
                </tr>
            </tbody>
            </table>
         
      </el-card>
      </el-col>

      <el-col :span="24" >
      <el-card>
          <template #header>
            <i ></i> 磁盘状态
          </template>
         
            <table cellspacing="0">
            <thead>
                <tr>
                  <th >磁盘名称</th>
                  <th >盘符路径</th>
                  <th >文件系统</th>
                  <th >盘符类型</th>
                  <th >总大小</th>
                  <th >已用大小</th>
                  <th >可用大小</th>
                  <th >已使用率</th>
                </tr>
            </thead>
            <tbody v-if="diskInfos" >
                <tr v-for="(sysFile, index) in diskInfos" :key="index">
                  <td >{{ sysFile.DiskName }}</td>
                  <td >{{ sysFile.RootPath }}</td>
                  <td >{{ sysFile.DriveType }}</td>
                  <td >{{ sysFile.FileSystem }}</td>
                  <td >{{ sysFile.TotalSize }}</td>
                  <td >{{ sysFile.UseSize }}</td>
                  <td >{{ sysFile.ResidueSize }}</td>
                  <td ><el-progress :percentage="sysFile.UseRate" :text-inside="true" :stroke-width="14" :color="customColor"/></td>
                </tr>
            </tbody>
            </table>
         
      </el-card>
      </el-col>




      <el-col :span="24" >
      <el-card >
          <template #header>
            <i ></i> 状态
          </template>
         
            <el-row>
            <el-col :xs="6" :sm="6" :md="6" :lg="6" :xl="6">
                CPU使用率
               
                  <el-progress type="dashboard" :percentage="cpuInfo.CPULoadVal" :color="customColor" />
               
         
                {{ cpuInfo.ProcessorCount }} 核心
            </el-col>

            
            <el-col :xs="6" :sm="6" :md="6" :lg="6" :xl="6">
                内存使用率
               
                  <el-progress type="dashboard" :percentage="memoryInfo.UsedPercentage" :color="customColor" />
               
                {{ memoryInfo.UsedPhysicalMemory }} / {{ memoryInfo.TotalPhysicalMemory }}
            </el-col>


            <el-col :xs="6" :sm="6" :md="6" :lg="6" :xl="6">
                网络监控上传
               
                  <el-progress type="dashboard" :percentage="networkInfo.SentSize" :color="customColor" >
                  <template #default>
                      {{networkInfo.SentSize}} {{ networkInfo.SentSizeType }}
                  </template>
                  </el-progress>
               
               {{networkInfo.SentSize}} {{ networkInfo.SentSizeType }}/S
            </el-col>


            <el-col :xs="6" :sm="6" :md="6" :lg="6" :xl="6">
                网络监控下载
               
                  <el-progress type="dashboard" :percentage="networkInfo.ReceivedSize" :color="customColor" >
                  <template #default>
                      {{networkInfo.ReceivedSize}} {{ networkInfo.ReceivedSizeType }}
                  </template>
                  </el-progress>
               
               {{networkInfo.ReceivedSize}} {{ networkInfo.ReceivedSizeType }}/S
            </el-col>
            </el-row>
         
      </el-card>
      </el-col>




      <el-col :span="24" >
      <el-row :gutter="25">
            <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" >
            <el-card >
                <template #header>
                  <i ></i> CPU使用率监控
                </template>
               
                  
               
            </el-card>
            </el-col>
            <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" >
            <el-card >
                <template #header>
                  <i ></i> 内存使用率监控
                </template>
               
                  
               
            </el-card>
            </el-col>
          </el-row>
      </el-col>

    </el-row>

</template>(5)效果展示:



原文链接:https://www.cnblogs.com/kimiliucn/p/17648543.html

来源:https://www.cnblogs.com/kimiliucn/archive/2023/08/22/17648543.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: SignalR实战:在.NET Framework和.NET Core中如何使用SignalR?