使用SignalR ASP.NET Core来简单实现一个后台实时推送数据给Echarts展现图表的功能

什么是 SignalR ASP.NET Core
javascript

ASP.NET Core SignalR 是一种开放源代码库,可简化将实时 web 功能添加到应用程序的功能。 实时 web 功能使服务器端代码能够当即将内容推送到客户端。html

SignalR ASP.NET Core能够作什么前端

• 须要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖、地图和 GPS 应用。
• 仪表板和监视应用。 示例包括公司仪表板、即时销售更新或旅行警报。
• 协做应用。 协做应用的示例包括白板应用和团队会议软件。
• 须要通知的应用。 社交网络、电子邮件、聊天、游戏、旅行警报和不少其余应用都需使用通知。java

SignalR  ASP.NET Core特点jquery

• 自动处理链接管理。
• 可将消息同时发送到全部链接的客户端。
• 可向特定客户端或客户端组发送消息。
• 可缩放以处理不断增长的流量。
• SignalR采用rpc来进行客户端与服务器端之间的通讯。
• SignalR会自动选择服务器和客户端的最佳传输方法(WebSockets、Server-Sent事件、长轮询)SignalR能够根据当前浏览器所支持的协议来选择最优的链接方式,从而可让咱们把更多的精力放在业务上而不是底层传输技术上。web

哪些浏览器支持SignalR  ASP.NET Coreajax

Apple Safari(包含IOS端)、Google Chrome(包括 Android端)、Microsoft Edge、Mozilla Firefox等主流浏览器都支持SignalR  ASP.NET Core。json

本次咱们将实现一个经过SignalR来简单实现一个后台实时推送数据给Echarts来展现图表的功能
后端

首先咱们新建一个ASP.NET Core 3.1的web应用浏览器

随后咱们引用SignalR  ASP.NET Core、Jquery和Echarts的客户端库

 

 

在项目中咱们新建如下目录

Class、HubInterface、Hubs

接着咱们在Pages目录下新建以下目录

echarts

在Shared目录中新建一个Razor布局页(_LayoutEcharts.cshtml)

在echarts目录中新建一个Razor页面(Index.cshtml)

在Class目录中新建一个类(ClientMessageModel.cs)

在HubInterface目录中新建一个接口(IChatClient.cs)

在Hub目录中新建一个类(ChatHub.cs)

咱们先实现后台逻辑代码,随后在编写前端交互代码。

在IChatClient.cs中,咱们主要是定义统一的服务端调用客户端方法的统一方法名(防止每次都要手动输入调用方法是出现失误而致使调用失败的低级错误)

namespace signalr.HubInterface
{
    public interface IChatClient
    {
        /// <summary>
        /// 客户端接收数据触发函数名
        /// </summary>
        /// <param name="clientMessageModel">消息实体类</param>
        /// <returns></returns>
        Task ReceiveMessage(ClientMessageModel clientMessageModel);
        /// <summary>
        /// Echart接收数据触发函数名
        /// </summary>
        /// <param name="data">JSON格式的能够被Echarts识别的data数据</param>
        /// <returns></returns>
        Task EchartsMessage(Array data);
        /// <summary>
        /// 客户端获取本身登陆后的UID
        /// </summary>
        /// <param name="clientMessageModel">消息实体类</param>
        /// <returns></returns>
        Task GetMyId(ClientMessageModel clientMessageModel);
    }
}

ClientMessageModel.cs中,咱们主要定义的是序列化后的交互用的实体类

namespace signalr.Class
{
    /// <summary>
    /// 服务端发送给客户端的信息
    /// </summary>
    [Serializable]
    public class ClientMessageModel
    {
        /// <summary>
        /// 接收用户编号
        /// </summary>
        public string UserId { get; set; }
        /// <summary>
        /// 组编号
        /// </summary>
        public string GroupName { get; set; }
        /// <summary>
        /// 发送的内容
        /// </summary>
        public string Context { get; set; }

    }
}

在ChatHub.cs中,主要是实现SignalR集线器的核心功能,用来处理客户端<==>服务器交互代码。在这里咱们继承了Hub<T>的方法,集成了咱们定义的IChatClient接口,从而就能够在方法中直接调用接口名称来和客户端交互。

namespace signalr.Hubs
{
    public class ChatHub : Hub<IChatClient>
    {
        public override async Task OnConnectedAsync()
        {
            var user = Context.ConnectionId;
 
            await Clients.Client(user).GetMyId(new ClientMessageModel { UserId = user, Context = $"回来了{DateTime.Now:yyyy-MM:dd HH:mm:ss}" });
            await Clients.AllExcept(user).ReceiveMessage(new ClientMessageModel { UserId = user, Context = $"进来了{DateTime.Now:yyyy-MM:dd HH:mm:ss}" });
            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception exception)
        {
            var user = Context.ConnectionId;
 
            await Clients.All.ReceiveMessage(new ClientMessageModel { UserId = user, Context = $"{user}离开了{DateTime.Now:yyyy-MM:dd HH:mm:ss}" });
            await base.OnDisconnectedAsync(exception);
        }
    }
}

咱们重写了Hub的OnConnectedAsync方法,当有客户端链接进来的时候,咱们给当前客户端发送一条“回来了”的内容,同时给全部在线的客户端发送一条“进来了”的通知,内容中会带上本次链接所分配给动态Guid编号。(相似与通知你们谁谁上线了)

在OnDisconnectedAsync方法中,当客户端断开链接的时候,会给全部在线客户端发送一条带有离线客户端的Guid的离开消息。(相似通知你们谁谁谁离开了)

在Startup.cs中,咱们作如下设置(注入SignalR和注册Hub),同时先把在DEBUG模式下的XSRF禁用,不然访问接口会提示400错误

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();
            services.AddRazorPages()
#if DEBUG
                //Debug下禁用XSRF防御,方便调试
                .AddRazorPagesOptions(o =>
                {
                    o.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
                })
#endif
                ;
        }

  

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapHub<ChatHub>("/chathub");//注册hub
            });
        } 

以上服务端的基架功能就搭建好了,下面咱们会来实现后台推送数据给前台Echart的功能。

在_LayoutEcharts.cshtml布局页中,咱们实现引用Jquery和Echarts的JS文件,同时编写一个请求后台接口的方法,调用这个方法后,后台就会主动推送屡次数据给前台。

<!DOCTYPE html>

<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width" />
    <script src="~/lib/echarts/dist/echarts.min.js"></script>
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <title>@ViewBag.Title</title>
    <script>
        function Test() {
            var chartDom = document.getElementById('main');
            var myChart = window.echarts.init(chartDom);
            $.ajax({
                url:'/echarts',
                type:'POST',
                dateType: 'json',
                data: { user: user},
                beforeSend: function (XHR) {
                    console.log('I am ' + user);
                    myChart.showLoading({
                        text: '加载中。。。',
                        effect: 'whirling'
                    });
                },
                success:function(data) {
                    var option = {
                        series: [{
                            data: data.data
                        }]
                    };
                    myChart.setOption(option);
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert(errorThrown);
                },
                complete:function(XHR, TS) {
                    myChart.hideLoading();
                }
            });
        }
    </script>
</head>
<body>
<div>
    @RenderBody()
</div>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

在echarts目录的Index.cshtml中,咱们实现引用Echarts组件,来渲染图表,引用SignalR来实现和服务器端数据实时交互。

@page
@model signalr.Pages.echarts.IndexModel
@{
    ViewBag.Title = "Echarts图标展现(https://www.cnblogs.com/wdw984)";
    Layout = "_LayoutEcharts";
}

<div id="main" style="width: 800px;height:600px;"></div>
<button onclick="Test()">测试</button>
<script type="text/javascript">
var app = {};

var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;

var posList = [
    'left', 'right', 'top', 'bottom',
    'inside',
    'insideTop', 'insideLeft', 'insideRight', 'insideBottom',
    'insideTopLeft', 'insideTopRight', 'insideBottomLeft', 'insideBottomRight'
];

app.configParameters = {
    rotate: {
        min: -90,
        max: 90
    },
    align: {
        options: {
            left: 'left',
            center: 'center',
            right: 'right'
        }
    },
    verticalAlign: {
        options: {
            top: 'top',
            middle: 'middle',
            bottom: 'bottom'
        }
    },
    position: {
        options: posList.reduce(function (map, pos) {
            map[pos] = pos;
            return map;
        }, {})
    },
    distance: {
        min: 0,
        max: 100
    }
};
app.config = {
    rotate: -25,
    align: 'left',
    verticalAlign: 'middle',
    position: 'bottom',
    distance: 15,
    onChange: function () {
        var labelOption = {
            normal: {
                rotate: app.config.rotate,
                align: app.config.align,
                verticalAlign: app.config.verticalAlign,
                position: app.config.position,
                distance: app.config.distance
            }
        };
        myChart.setOption({
            series: [{
                label: labelOption
            }, {
                label: labelOption
            }, {
                label: labelOption
            }, {
                label: labelOption
            }]
        });
    }
};
var labelOption = {
    show: true,
    position: app.config.position,
    distance: app.config.distance,
    align: app.config.align,
    verticalAlign: app.config.verticalAlign,
    rotate: app.config.rotate,
    formatter: '{c}  {name|{a}}',
    fontSize: 16,
    rich: {
        name: {
        }
    }
};

option = {
    title: {
        text: '验证状况统计'
    },
    tooltip: {},
    legend: {
        
    },
    xAxis: {
        data: ['数据一','数据二', '数据三','',
            '数据四', '数据五','',
            '数据六', '数据七', '数据八','数据九','',
            '数据十','数据十一','数据十二','数据十三','数据十四'],
        axisTick: {show: false},
        axisLabel:{rotate: -25,interval: 0}
    },
    yAxis: {},
    series: [{
        type: 'bar',
        label: {
            show: true,
            position: 'outside'
        },
        itemStyle: {
            normal: {                
                color: function(params) {
                    var colorList = [
                        "Blue",
                        "Blue",
                        "Blue",
                        "",
                        "LightSkyBlue",
                        "LightSkyBlue",
                        "",
                        "Gold",
                        "Gold",
                        "Gold",
                        "Gold",
                        "",
                        "LightGrey",
                        "LightGrey",
                        "LightGrey",
                        "LightGrey",
                        "LightGrey"
                    ];
                    return colorList[params.dataIndex];
                }
            }
        },
        data: ['0','0','0','', '0', '0', '', '0','0','0','0','', '0','0','0','0','0']
    }]
};

option && myChart.setOption(option);
 
</script>
@section Scripts
{
    <script src="~/js/signalr/dist/browser/signalr.js"></script>
    <script src="~/js/echartchat.js"></script>
}

在Index后台代码中,咱们响应一个POST请求,请求中带上SignalR分配的惟一编号,后台模拟数据统计,推送给前台,这里用Task.Factory来建立一个任务执行这个操做。

        public async Task<JsonResult> OnPostAsync(string user)
        {
            if (string.IsNullOrWhiteSpace(user))
            {
                return new JsonResult(new { status = "fail", message = "NoUser" });
            }
            await Task.Factory.StartNew(async () =>
            {
                var rnd = new Random(DateTime.Now.Millisecond);
                for (var i = 0; i < 10; i++)
                {
                    await _hubContext.Clients.Client(user)
                        .EchartsMessage(
                            new[] {
                                        $"{rnd.Next(100,300)}",
                                        $"{rnd.Next(100,320)}" ,
                                        $"{rnd.Next(100,310)}",
                                        "",
                                        $"{rnd.Next(10,30)}",
                                        $"{rnd.Next(10,30)}",
                                        "",
                                        $"{rnd.Next(130,310)}",
                                        $"{rnd.Next(130,310)}",
                                        $"{rnd.Next(13,31)}",
                                        $"{rnd.Next(13,31)}",
                                        "",
                                        $"{rnd.Next(130,310)}",
                                        $"{rnd.Next(130,310)}",
                                        $"{rnd.Next(13,31)}",
                                        $"{rnd.Next(130,310)}",
                                        $"{rnd.Next(130,310)}"}
                            );
                    await Task.Delay(2000);
                }
            }, TaskCreationOptions.LongRunning);

            return new JsonResult(new { status = "ok" });
        }

随后咱们访问如下这个页面,就能够看到目前这种效果

 

 

 下面咱们来编写前端js,用来和后端服务经过SignalR通讯,在wwwroot/js下新建一个echartchat.js 

"use strict";
var connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Debug)
    .build();
var user = "";
var chartDom = document.getElementById('main');
var myChart = window.echarts.init(chartDom);

connection.on("GetMyId", function (data) {
    user = data.userId;//SignalR返回的数据字段开头是小写
    console.log(user);
});
connection.on("ReceiveMessage", function (data) {
    console.log(data.userId + data.context);
});

connection.on("EchartsMessage", function (data) {
    console.log(data);
    var option = {
        series: [{
            data: data
        }]
    };
    myChart.setOption(option);//更新Echarts数据
});

connection.start().then(function () {
    console.log("服务器已链接");
}).catch(function (err) {
    return console.error(err.toString());
});

保存后咱们再次访问页面,并点击按钮,就能够实现后台推送数据给前台echarts来展现图标的效果。