zoukankan      html  css  js  c++  java
  • 【SignalR学习系列】6. SignalR Hubs Api 详解(C# Server 端)

    如何注册 SignalR 中间件

    为了让客户端能够连接到 Hub ,当程序启动的时候你需要调用 MapSignalR 方法。

    下面代码显示了如何在 OWIN startup 类里面定义 SignalR Hubs 路由。

    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(MyApplication.Startup))]
    namespace MyApplication
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                // Any connection or hub wire up and configuration should go here
                app.MapSignalR();
            }
    
        }
    }

    The /signalr URL

    默认情况下,客户端都是通过 "/signalr" 路由地址来连接到你的 Hub,你也可以修改不使用默认的 "/signalr"。

    服务端代码指定Url

    app.MapSignalR("/signalrTest", new HubConfiguration());

    JavaScript 客户端代码指定Url

    <script src="signalrTest/hubs"></script>
    
    var chat = $.connection.chatHub;
    chat.url = "/signalrTest";

     .NET 客户端代码指定Url

    var Connection = new HubConnection("http://localhost:8080/signalrTest");

    如何创建并使用 Hub 类

    为了创建 Hub 类,你需要创建一个继承于 Microsoft.Aspnet.Signalr.Hub 的类

    public class ContosoChatHub : Hub
    {
        public void NewContosoChatMessage(string name, string message)
        {
            Clients.All.addNewMessageToPage(name, message);
        }
    }

    JavaScript 端的 Hub 名字

    Server (驼峰命名法,第一个字母可以为大写也可以为小写)

    public class ContosoChatHub : Hub

    JavaScript (不管 Server 端第一个字母为大小还是小写,JavaScript 客户端必须是小写,不然就找不到对应的 Hub)

    var contosoChatHubProxy = $.connection.contosoChatHub;

     当使用 特性 HubName命名 Hub 的名字,那么 Server 端和 Client 端的 Hub 名大小写必须保持一致。

     Server (如果 HubName 指定的第一个字母为大写,那么 JavaScript 端也必须为大写。如果是小写,那么 JavaScript 端也必须为小写)

    [HubName("PascalCaseContosoChatHub")]
    public class ContosoChatHub : Hub

    JavaScript 

    var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;

    强类型的 Hub

     定义一个接口 T,让你的 Hub 类继承于 Hub<T>,这样就可以指定你的客户端可以调用的方法,也可以在你的 Hub 方法里开启代码提示。

    public class StrongHub : Hub<IClient>
    {
        public void Send(string message)
        {
            Clients.All.NewMessage(message);
        }
    }
    
    public interface IClient
    {
        void NewMessage(string message);
    }

    如何在 Hub 类里定义客户端可以调用的方法

    如果需要暴露一个可以在客户端调用的方法,那么需要在 Hub 里定义一个 public 的方法,如下所示。

    public class ContosoChatHub : Hub
    {
        public void NewContosoChatMessage(string name, string message)
        {
            Clients.All.addNewMessageToPage(name, message);
        }
    }
    public class StockTickerHub : Hub
    {
        public IEnumerable<Stock> GetAllStocks()
        {
            return _stockTicker.GetAllStocks();
        }
    }

    你可以指定方法的参数和返回类型,包含复杂类型和数组。这些数据在客户端和服务端会使用 Json 来进行传输,SignalR 会自动绑定复杂类型对象和数组对象。

    Hub 里的方法名(非客户端方法名)

    Server (驼峰命名法,第一个字母可以为大写也可以为小写)

    public void NewContosoChatMessage(string userName, string message)

    JavaScript (不管 Server 端第一个字母为大小还是小写,JavaScript 客户端必须是小写,不然就找不到对应的 方法)

    contosoChatHubProxy.server.newContosoChatMessage(userName, message);

    使用特性 HubMethodName 指定方法名,那么 Server 端和 Client 端的 方法名大小写必须保持一致。

    Server 端

    [HubMethodName("PascalCaseNewContosoChatMessage")]
    public void NewContosoChatMessage(string userName, string message)

    JavaScript 端

    contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

    如何在 Hub 类中调用客户端的方法

    你可以在 Hub 类方法中使用 Clients 属性来调用客户端的方法

    Server 端

    public class ContosoChatHub : Hub
    {
        public void NewContosoChatMessage(string name, string message)
        {
            Clients.All.addNewMessageToPage(name, message);
        }
    }

    JavaScript 端

    contosoChatHubProxy.client.addNewMessageToPage = function (name, message) {
        // Add the message to the page. 
        $('#discussion').append('<li><strong>' + htmlEncode(name)
            + '</strong>: ' + htmlEncode(message) + '<li>');
    };

    你不能从客户端得到一个返回值,比如 int x = Clients.All.add(1,1) 是没有用的。

    你可以给参数指定复杂类型和数组类型,如下所示。

    Sever 端

    public void SendMessage(string name, string message)
    {
        Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
    }
    
    public class ContosoChatMessage
    {
        public string UserName { get; set; }
        public string Message { get; set; }
    }

    JavaScript 代码

    var contosoChatHubProxy = $.connection.contosoChatHub;
    contosoChatHubProxy.client.addMessageToPage = function (message) {
        console.log(message.UserName + ' ' + message.Message);
    });

    指定哪些客户端接收信息

    所有连接的客户端

    Clients.All.addContosoChatMessageToPage(name, message);

    只是 Calling 的客户端

    Clients.Caller.addContosoChatMessageToPage(name, message);

    所有连接的客户端除了 Calling 的客户端

    Clients.Others.addContosoChatMessageToPage(name, message);

    通过 connection ID 指定特定的客户端

    Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);

    通过 connection ID 排除特定的客户端

    Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);

    指定一个特殊组

    Clients.Group(groupName).addContosoChatMessageToPage(name, message);

    指定一个特殊组,并且排除特定 connection ID 的客户端

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);

    指定一个特殊组,但是排除 calling

    Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name, message);

    通过 userId 指定一个特殊的用户,一般情况下是 IPrincipal.Identity.Name

    Clients.User(userid).addContosoChatMessageToPage(name, message);

    在一个 connection IDs 列表里的所有客户端和组

    Clients.Clients(ConnectionIds).broadcastMessage(name, message);

    指定多个组

    Clients.Groups(GroupIds).broadcastMessage(name, message);

    通过用户名

    Clients.Client(username).broadcastMessage(name, message);

    一组用户名

    Clients.Users(new string[] { "myUser", "myUser2" }).broadcastMessage(name, message);

    大小写不敏感

    客户端方法名的调用是大小写不敏感的,比如 Clients.All.addContosoChatMessageToPage 会调用客户端的 AddContosoChatMessageToPage, addcontosochatmessagetopage, or addContosoChatMessageToPage 这些方法。

    异步执行

    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.Others.addContosoChatMessageToPage(data);
        Clients.Caller.notifyMessageSent();
    }

    如何使用一个 string 变量作为方法名

    如果你需要使用一个 string 作为方法名,那么你需要把 Clients.All (or Clients.Others, Clients.Caller, etc.)  对象赋值给 IClientProxy,然后调用它的 Invoke(methodName, args...) 方法。

    public void NewContosoChatMessage(string name, string message)
    {
        string methodToCall = "addContosoChatMessageToPage";
        IClientProxy proxy = Clients.All;
        proxy.Invoke(methodToCall, name, message);
    }

    管理组成员关系

    Server 端

    public class ContosoChatHub : Hub
    {
        public Task JoinGroup(string groupName)
        {
            return Groups.Add(Context.ConnectionId, groupName);
        }
    
        public Task LeaveGroup(string groupName)
        {
            return Groups.Remove(Context.ConnectionId, groupName);
        }
    }

    客户端

    contosoChatHubProxy.server.joinGroup(groupName);
    contosoChatHubProxy.server.leaveGroup(groupName);

    异步执行

    public async Task JoinGroup(string groupName)
    {
        await Groups.Add(Context.ConnectionId, groupName);
        Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
    }

    如何在 Hub 类里面捕获和处理连接生命周期事件

    public class ContosoChatHub : Hub
    {
        public override Task OnConnected()
        {
            // Add your own code here.
            // For example: in a chat application, record the association between
            // the current connection ID and user name, and mark the user as online.
            // After the code in this method completes, the client is informed that
            // the connection is established; for example, in a JavaScript client,
            // the start().done callback is executed.
            return base.OnConnected();
        }
    
        public override Task OnDisconnected()
        {
            // Add your own code here.
            // For example: in a chat application, mark the user as offline, 
            // delete the association between the current connection id and user name.
            return base.OnDisconnected();
        }
    
        public override Task OnReconnected()
        {
            // Add your own code here.
            // For example: in a chat application, you might have marked the
            // user as offline after a period of inactivity; in that case 
            // mark the user as online again.
            return base.OnReconnected();
        }
    }

    如何从 Content 里面获取 Clients 信息

    Calling 客户端的 connection ID

    string connectionID = Context.ConnectionId;

    connection ID 是一个由SignalR分配的 GUID  ( 你不能用自己的代码指定 ). 每个连接都有一个 connection ID , 如果你的应用里包含多个 Hubs,那么多个 Hubs也会共用同一个 connection ID .

    Http Header 数据

    System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
    System.Collections.Specialized.NameValueCollection headers = Context.Headers;

    Query string 数据

    System.Collections.Specialized.NameValueCollection queryString = Context.Request.QueryString;
    System.Collections.Specialized.NameValueCollection queryString = Context.QueryString;
    string parameterValue = queryString["parametername"];

    JavaScript 客户端 QueryString

    $.connection.hub.qs = { "version" : "1.0" };

    这边的 $.connection.hub.qs 不是指你当前的 Hub ($.connection.chatHub.qs)。

    Cookies

    System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.Request.Cookies;
    System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.RequestCookies;

    用户信息

    System.Security.Principal.IPrincipal user = Context.User;

    Request 的 HttpContext 对象

    System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();

    如何在客户端和服务端传递 State

    JavaScript 客户端

    contosoChatHubProxy.state.userName = "Fadi Fakhouri";
    contosoChatHubProxy.state.computerName = "fadivm1";

    Server 端获取,可以使用 Caller 或者 CallerState 两种方式

    string userName = Clients.Caller.userName;
    string computerName = Clients.Caller.computerName;
    string userName = Clients.CallerState.userName;
    string computerName = Clients.CallerState.computerName;

    如何在 Hub 类中捕获异常

    • 使用 try - catch 并记录日志
    • 创建一个能处理OnIncomingError的 Hubs 管道模型
      • public class ErrorHandlingPipelineModule : HubPipelineModule
        {
            protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
            {
                Debug.WriteLine("=> Exception " + exceptionContext.Error.Message);
                if (exceptionContext.Error.InnerException != null)
                {
                    Debug.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message);
                }
                base.OnIncomingError(exceptionContext, invokerContext);
        
            }
        }
        
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); 
            app.MapSignalR();
        }
    • 使用 HubException 类,抛出异常
      • public class MyHub : Hub
        {
            public void Send(string message)
            {
                if(message.Contains("<script>"))
                {
                    throw new HubException("This message will flow to the client", new { user = Context.User.Identity.Name, message = message });
                }
        
                Clients.All.send(message);
            }
        }

    启用日志诊断

    如果需要启动 Server 端日志,那么需要在 web.config 里面添加一个 system.diagnostics 节点

    <system.diagnostics>
        <sources>
          <source name="SignalR.SqlMessageBus">
            <listeners>
              <add name="SignalR-Bus" />
            </listeners>
          </source>
         <source name="SignalR.ServiceBusMessageBus">
            <listeners>
                <add name="SignalR-Bus" />
            </listeners>
         </source>
         <source name="SignalR.ScaleoutMessageBus">
            <listeners>
                <add name="SignalR-Bus" />
            </listeners>
          </source>
          <source name="SignalR.Transports.WebSocketTransport">
            <listeners>
              <add name="SignalR-Transports" />
            </listeners>
          </source>
          <source name="SignalR.Transports.ServerSentEventsTransport">
              <listeners>
                  <add name="SignalR-Transports" />
              </listeners>
          </source>
          <source name="SignalR.Transports.ForeverFrameTransport">
              <listeners>
                  <add name="SignalR-Transports" />
              </listeners>
          </source>
          <source name="SignalR.Transports.LongPollingTransport">
            <listeners>
                <add name="SignalR-Transports" />
            </listeners>
          </source>
          <source name="SignalR.Transports.TransportHeartBeat">
            <listeners>
                <add name="SignalR-Transports" />
            </listeners>
          </source>
        </sources>
        <switches>
          <add name="SignalRSwitch" value="Verbose" />
        </switches>
        <sharedListeners>
          <add name="SignalR-Transports" 
               type="System.Diagnostics.TextWriterTraceListener" 
               initializeData="transports.log.txt" />
            <add name="SignalR-Bus"
               type="System.Diagnostics.TextWriterTraceListener"
               initializeData="bus.log.txt" />
        </sharedListeners>
        <trace autoflush="true" />
      </system.diagnostics>

    如何在 Hub 类外面调用客户端方法和管理组

    如果需要在 Hub 类外面调用客户端方法和管理组,那么需要获取一个 SignalR context 对象,然后可以通过 context.Clients 对象来操作客户端方法和组。

    IHubContext _context = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
    _context.Clients.All.updateStockPrice(stock);

    如何自定义管道模型

    SignalR 允许你把自己的代码注入 Hub 管道模型,你可以通过继承 HubPipelineModule 来实现

    public class LoggingPipelineModule : HubPipelineModule 
    { 
        protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context) 
        { 
            Debug.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name); 
            return base.OnBeforeIncoming(context); 
        }   
        protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context) 
        { 
            Debug.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub); 
            return base.OnBeforeOutgoing(context); 
        } 
    }

    然后在 Startup.cs 里注入自定义的 Hub 管道模型

    public void Configuration(IAppBuilder app) 
    { 
        GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); 
        app.MapSignalR();
    }

    参考链接:

    https://docs.microsoft.com/zh-cn/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server

  • 相关阅读:
    PHP全栈学习笔记3
    PHP全栈学习笔记3
    JavaScript基础系列
    JavaScript基础系列
    Java语言
    Java语言
    HTML基础系列
    HTML基础系列
    PHP全栈从入门到精通1
    PHP全栈从入门到精通1
  • 原文地址:https://www.cnblogs.com/Soulless/p/7235524.html
Copyright © 2011-2022 走看看