JavaScript客户端

ASP.NET Core SignalR JavaScript 客户端

作者:Rachel Appel

ASP.NET Core SignalR JavaScript 客户端库使开发人员能够调用服务器端集线器代码。

查看或下载示例代码如何下载

安装 SignalR 客户端包

SignalR JavaScript 客户端库作为npm包提供。 如果使用 Visual Studio,运行npm install程序包管理器控制台时的根文件夹中。 对于 Visual Studio Code 中,运行中的命令集成终端

npm init -y
npm install @microsoft/signalr

npm 安装中的包内容 node_modules\@microsoft\signalr\dist\browser 文件夹。 创建一个名为的新文件夹signalrwwwroot\lib文件夹。 复制 signalr.js 的文件 wwwroot\lib\signalr 文件夹。

npm init -y
npm install @aspnet/signalr

npm 安装中的包内容 node_modules\@aspnet\signalr\dist\browser 文件夹。 创建一个名为的新文件夹signalrwwwroot\lib文件夹。 复制 signalr.js 的文件 wwwroot\lib\signalr 文件夹。

使用 SignalR JavaScript 客户端

引用 <script> 元素中的 SignalR JavaScript 客户端。

<script src="~/lib/signalr/signalr.js"></script>

连接到中心

下面的代码创建并启动连接。 在中心的名称是不区分大小写。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

connection.start().then(function () {
    console.log("connected");
});

跨域的连接

通常情况下,浏览器请求的页面所在的域从加载的连接。 但是,有一些情况下需要与另一个域的连接时。

若要防止恶意站点读取另一个站点中的敏感数据跨域连接默认处于禁用状态。 若要允许跨域请求,请启用它在Startup类。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddMvc();

            services.AddCors(options => options.AddPolicy("CorsPolicy", 
            builder => 
            {
                builder.AllowAnyMethod().AllowAnyHeader()
                       .WithOrigins("http://localhost:55830")
                       .AllowCredentials();
            }));

            services.AddSignalR();
        }

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

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseCors("CorsPolicy");
            app.UseSignalR(routes => 
            {
                routes.MapHub<ChatHub>("/chathub");
            });
            app.UseMvc();            
        }
    }
}

从客户端调用集线器方法

JavaScript 客户端上中心通过调用公共方法调用方法HubConnection invoke方法接受两个参数:

  • 集线器方法的名称。 在以下示例中,中心上的方法名称是SendMessage

  • 在集线器方法中定义的任何参数。 在以下示例中,参数名称是message 示例代码使用了在所有主要浏览器(Internet Explorer 除外)的当前版本中受支持的箭头函数语法。

    connection.invoke("SendMessage", user, message).catch(err => console.error(err.toString()));
    

备注

如果在无服务器模式下使用 Azure SignalR 服务,则无法从客户端调用集线器方法。 有关详细信息,请参阅SignalR 服务文档

invoke方法返回 JavaScript 承诺 当服务器上的方法返回时,将用返回值(如果有)解析 Promise 如果服务器上的方法引发错误,则会拒绝 Promise 错误消息。 使用 Promise 本身的 thencatch 方法来处理这些情况(或 await 语法)。

send 方法返回 JavaScript Promise 当消息发送到服务器时,将解析 Promise 如果发送消息时出错,则会拒绝 Promise,并会出现错误消息。 使用 Promise 本身的 thencatch 方法来处理这些情况(或 await 语法)。

备注

使用 send 不会等到服务器收到消息。 因此,不可能从服务器返回数据或错误。

从集线器调用客户端方法

若要从中心接收消息,定义方法使用方法的HubConnection

  • JavaScript 客户端方法的名称。 在以下示例中,方法名称是ReceiveMessage
  • 该中心将传递给方法的参数。 在以下示例中,参数值是message
connection.on("ReceiveMessage", (user, message) => {
    const encodedMsg = user + " says " + message;
    const li = document.createElement("li");
    li.textContent = encodedMsg;
    document.getElementById("messagesList").appendChild(li);
});

在前面的代码connection.on运行时服务器端代码将调用它使用SendAsync方法。

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

SignalR 通过匹配 SendAsyncconnection.on中定义的方法名称和参数来确定要调用的客户端方法。

备注

最佳做法是,调用启动方法HubConnectionon 这样做可确保您的处理程序注册之前接收任何消息。

错误处理和日志记录

catch方法的末尾start方法以处理客户端错误。 使用console.error向浏览器的控制台输出错误。

connection.start().catch(function (err) {
    return console.error(err.toString());
});

通过传递要进行连接时记录的记录器和事件类型的安装程序客户端的日志跟踪。 使用指定的日志级别和更高版本记录的消息。 可用日志级别为按如下所示:

  • signalR.LogLevel.Error – 错误消息。 日志Error仅消息。
  • signalR.LogLevel.Warning – 有关潜在错误的警告消息。 日志Warning,和Error消息。
  • signalR.LogLevel.Information – 不包含错误的状态消息。 日志InformationWarning,和Error消息。
  • signalR.LogLevel.Trace – 跟踪消息。 记录所有内容,包括数据中心和客户端之间传输。

使用configureLogging方法HubConnectionBuilder若要配置日志级别。 消息会记录到浏览器控制台。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

重新连接客户端

自动重新连接

可以将 SignalR 的 JavaScript 客户端配置为使用HubConnectionBuilder上的 withAutomaticReconnect 方法自动重新连接。 默认情况下,它不会自动重新连接。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withAutomaticReconnect()
    .build();

如果没有任何参数,withAutomaticReconnect() 会将客户端配置为分别等待0、2、10和30秒,然后再尝试重新连接尝试。

在开始任何重新连接尝试之前,HubConnection 将转换为 HubConnectionState.Reconnecting 状态,并激发其 onreconnecting 回调,而不是转换到 Disconnected 状态并触发其 onclose 回调,如未配置自动重新连接的 HubConnection 这为用户提供警告连接已丢失并禁用 UI 元素的机会。

connection.onreconnecting((error) => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messagesList").appendChild(li);
});

如果客户端在其前四次尝试内成功重新连接,则 HubConnection 将转换回 Connected 状态,并激发其 onreconnected 回调。 这为用户提供了通知用户连接已重新建立的机会。

由于连接完全是服务器的新内容,因此向 onreconnected 回调提供了一个新的 connectionId

警告

如果 HubConnection配置为跳过协商,则onreconnected不会定义回调的connectionId参数。

connection.onreconnected((connectionId) => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messagesList").appendChild(li);
});

withAutomaticReconnect() 不会将 HubConnection 配置为重试初始启动失败,因此,需要手动处理启动失败:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("connected");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

如果客户端在其前四次尝试中未成功重新连接,则 HubConnection 将转换为 Disconnected 状态,并激发其onclose回调。 这为用户提供了通知用户连接永久丢失的机会,并建议刷新页面:

connection.onclose((error) => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messagesList").appendChild(li);
});

为了在断开连接或更改重新连接时间安排之前配置自定义的重新连接尝试次数,withAutomaticReconnect 接受一个数字数组,表示在开始每次重新连接尝试之前等待的延迟(以毫秒为单位)。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

前面的示例将 HubConnection 配置为在连接丢失后立即开始尝试重新连接。 这也适用于默认配置。

如果第一次重新连接尝试失败,则第二次重新连接尝试还会立即启动,而不是等待2秒,就像在默认配置中一样。

如果第二次重新连接尝试失败,则第三次重新连接尝试将在10秒内启动,这与默认配置相同。

然后,在第三次重新连接尝试失败后,自定义行为将再次从默认行为与其分离,而不是在另一个30秒内尝试再次尝试重新连接,就像在默认配置中一样。

如果需要更好地控制计时和自动重新连接尝试的次数,withAutomaticReconnect 接受一个实现 IRetryPolicy 接口的对象,该对象具有名为 nextRetryDelayInMilliseconds的单个方法。

nextRetryDelayInMilliseconds 采用 RetryContext类型的单个自变量。 RetryContext 具有三个属性: previousRetryCountelapsedMillisecondsretryReason 分别为 numbernumberError 第一次重新连接尝试之前,previousRetryCountelapsedMilliseconds 均为零,retryReason 将是导致连接丢失的错误。 每次重试失败后,previousRetryCount 会递增1,elapsedMilliseconds 将更新以反映到目前为止的重新连接时间(以毫秒为单位),retryReason 将是导致上次重新连接尝试失败的错误。

nextRetryDelayInMilliseconds 必须返回一个数字,该数字表示在下一次重新连接尝试之前要等待的毫秒数,或者,如果 HubConnection 应停止重新连接,则为 null

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

或者,你可以编写将手动重新连接客户端的代码,如手动重新连接中所示。

手动重新连接

警告

在3.0 之前,SignalR 的 JavaScript 客户端不会自动重新连接。 必须编写代码将手动重新连接你的客户端。

下面的代码演示典型的手动重新连接方法:

  1. 一个函数 (在这种情况下,start函数) 创建以启动连接。
  2. 调用start中的连接函数onclose事件处理程序。
async function start() {
    try {
        await connection.start();
        console.log("connected");
    } catch (err) {
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

connection.onclose(async () => {
    await start();
});

实际的实现会使用指数退让或重试的次数后放弃了指定的次数。

其他资源