我一直觉得学习的最好方法就是先让程序能够正常运行,才去学习他的原理,剖析他的细节.

就好像这个图:

ASP.NET Core 2.2 基础知识(十六) SignalR (未完待续)-LMLPHP

所以,我们先跟着官方文档,创建一个 SignalR 应用: https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&tabs=visual-studio

这个例子一共涉及到下面几个步骤:

  • 自定义中心 ChatHub ;
  • 在启动类 Startup 中启用 SignalR 服务,并添加路由;
  • 编写客户端JS
  • 下载 SignalR 官方JS.

自定义中心 : ChatHub

    public class ChatHub : Hub
    {    
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }

"Hub" 一词,有的地方翻译成"集线器",有的地方翻译成"中心",对于我这种非科班出身的人来说,还是"中心"听起简单点.

对于基类 Hub ,xml 是这样说的 : A base class for a SignalR hub.   SignalR 中心的基类.

这个很简单,看这些名字就知道他们是干嘛用的,具体的描述可以看官方文档.

ASP.NET Core 2.2 基础知识(十六) SignalR (未完待续)-LMLPHP

中心的作用可以这样简单的描述:

通过中心,我们能在服务器的代码中定义客户端可以调用的方法(必须是 public);

通过中心,我们能在客户端的代码中定义服务器可以调用的方法.

上述代码,我们通过继承 Hub ,定义了一个自己的中心 : ChatHub (聊天中心) ,在这个类里面,我们做了下面两件事:

  • 定义了客户端可以调用的方法 : SendMessage(string user, string message)
  • 调用了所有连接上 Hub 的客户端的 ReceiveMessage 方法,并将 user,message 两个字符串作为入参传入该方法.

中心定义好了,肯定需要启用

在启动类 Startup 中启用 SignalR 服务,并添加路由

        public void ConfigureServices(IServiceCollection services)
        {
            ......

            //注册 SignalR 服务
            services.AddSignalR();
        }
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            ......

            //设置 SignalR 中心路由
            app.UseSignalR(routes => { routes.MapHub<ChatHub>("/chatHub"); });

            app.UseMvc();
        }

编写客户端JS 

"use strict";//不太明白这句话是什么意思...

//创建一个连接到我们创建的 ChatHub 的 connection.
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();

//定义客户端的方法,方法名: ReceiveMessage ,两个入参.
//注意,这个方法就是服务器要调用的方法,服务器和客户端的名字一定要一样.
connection.on("ReceiveMessage", function (user, message) {
    var msg = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    var encodedMsg = user + " says " + msg;
    var li = document.createElement("li");
    li.textContent = encodedMsg;
    document.getElementById("messagesList").appendChild(li);
});

//开启连接.
connection.start().catch(function (err) {
    return console.error(err.toString());
});

document.getElementById("sendButton").addEventListener("click", function (event) {
    var user = document.getElementById("userInput").value;
    var message = document.getElementById("messageInput").value;

    //调用服务器的 SendMessage 方法,并传入两个入参.这和委托的调用太像了.
    connection.invoke("SendMessage", user, message).catch(function (err) {
        return console.error(err.toString());
    });
    event.preventDefault();
});

上面的 ChatHub 类的 SendAsync 方法在调用客户端的方法时,方法名是直接写的 字符串 : "ReceiveMessage"

官方不推荐这样写,因为不是强类型,可能出现运行时错误,因此建议使用强类型的中心

(顺带附上了一些额外的功能):

    //建议使用下面的强类型方式
    //方法二
    public interface IChatClient
    {
        //就算是这种强类型方式,客户端定义的方法名也必须和这个方法名一样,包括签名.
        Task ReceiveMessage(string user, string message);
    }

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        //[HubMethodName("hello")] 可以改名,如果改了名,前端也要跟着改,别忘了.
        public async Task SendMessage(string user, string message)
        {
            //调用客户端定义的 ReceiveMessage 方法.
            //throw new HubException("哈哈,出错了!");//可以向客户端发送异常.只会向当前调用的客户端发送,并且只发送 message ,不会发送堆栈信息.
            await Clients.All.ReceiveMessage($"{GetHashCode()}" + user, message);//传递 hashCode 是为了证明,每次调用都是不同的实例.所以官方说不要在"中心"里面存状态.
        }

        //该方法可以在客户端连接上后,执行操作
        public override async Task OnConnectedAsync()
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
            await base.OnConnectedAsync();
        }

        //同理,当客户端断开连接时执行的操作
        public override async Task OnDisconnectedAsync(Exception exception)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
            await base.OnDisconnectedAsync(exception);
        }
    }

当然,注册路由的代码也得换了 :  app.UseSignalR(routes => { routes.MapHub<StronglyTypedChatHub>("/chatHub"); });

好困,明天继续

01-09 06:11