ASP.NET Core SignalR JavaScript 客户端

作者:Rachel Appel

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

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

安装 SignalR 客户端包

SignalRJavaScript 客户端库以npm包的形式提供。 以下部分概述了安装客户端库的不同方式。

通过 npm 安装

对于 Visual Studio,请在根文件夹中的 " 包管理器控制台 " 中运行以下命令。 对于 Visual Studio Code,请从 集成终端 运行以下命令。

npm init -y
npm install @microsoft/signalr

npm 将包内容安装到 *node_modules \ @microsoft\signalr\dist\browser* 文件夹中。 在 wwwroot \ lib 文件夹下创建名为 signalr 的新文件夹。 将 signalr.js 文件复制到 wwwroot\lib\signalr 文件夹。

SignalR在元素中引用 JavaScript 客户端 <script> 。 例如:

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

使用内容交付网络 (CDN)

若要在不使用 npm 先决条件的情况下使用客户端库,请引用 CDN 托管的客户端库副本。 例如:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.min.js"></script>

以下 Cdn 提供了客户端库:

使用 LibMan 安装

LibMan 可用于从 CDN 托管的客户端库安装特定客户端库文件。 例如,仅将缩小的 JavaScript 文件添加到项目。 有关该方法的详细信息,请参阅 添加 SignalR 客户端库

连接到中心

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

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

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(start);

// Start the connection.
start();

跨源连接

通常,浏览器从请求页面的同一个域加载连接。 但是,有时需要连接到另一个域。

重要

客户端代码必须使用绝对 URL 而不是相对 URL。 将 .withUrl("/chathub") 更改为 .withUrl("https://myappurl/chathub")

为了防止恶意站点从另一站点读取 敏感数据,默认情况下 禁用跨源连接。 若要允许跨源请求,请启用它在 类 Startup 中:

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

namespace SignalRChat
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddSignalR();

            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("https://example.com")
                        .AllowCredentials();
                });
            });
        }

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

            app.UseStaticFiles();
            app.UseRouting();

            app.UseCors();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapHub<ChatHub>("/chathub");
            });
        }
    }
}

从客户端调用中心方法

JavaScript 客户端通过HubConnectioninvoke方法在中心调用公共方法。 方法 invoke 接受:

  • 中心方法的名称。
  • 中心方法中定义的任何参数。

在下面的示例中,中心上的方法名称为 SendMessage 。 传递给映射到中心方法 的 和 参数的第二个 invoke 和第 usermessage 个参数:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

备注

仅 SignalR 在 默认 模式下使用 Azure 服务时,才支持从客户端调用中心方法。 有关详细信息,请参阅 Signalr GitHub 存储库) (常见问题解答

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

JavaScript 客户端也可以通过的 send 方法在集线器上调用公共方法 HubConnection 。 与 invoke 方法不同, send 方法不会等待服务器的响应。 send方法返回 JavaScript PromisePromise当消息已发送到服务器时,将解决。 如果发送消息时出错,将 Promise 拒绝,并出现错误消息。 使用 asyncawait 或的 Promise thencatch 方法来处理这些情况。

备注

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

从中心调用客户端方法

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

  • JavaScript 客户端方法的名称。
  • 集线器传递给方法的参数。

在下面的示例中,方法名称是 ReceiveMessage 。 参数名称为 usermessage

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

当服务器端代码使用 方法调用它时,中前面的 connection.on 代码 SendAsync 将运行:

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

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

备注

最佳做法是,在 之后调用 上的 HubConnection start 方法 on 。 这样做可确保在接收任何消息之前注册处理程序。

错误处理和日志记录

将 和 与 和 或 的 方法一起 try catch async await Promise catch 用于处理客户端错误。 使用 console.error 将错误输出到浏览器的控制台:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

通过传递记录器以及建立连接时要记录的事件类型来设置客户端日志跟踪。 使用指定的日志级别和更高级别记录消息。 可用的日志级别如下所示:

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

使用HubConnectionBuilder上的configureLogging方法配置日志级别。 消息将记录到浏览器控制台:

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

重新连接客户端

自动重新连接

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

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 回调。 这为用户提供了通知用户连接已重新建立的机会。

由于连接在服务器上看起来是全新的,因此 connectionId 将向回调提供一个新的 onreconnected

警告

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

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("SignalR 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 采用类型为 的单个参数 RetryContextRetryContext具有三个属性 previousRetryCount :、 elapsedMillisecondsretryReason ,分别是 number 、 和 number Error 。 首次重新连接尝试之前, 和 都将为零,并且 将是导致连接丢失 previousRetryCount elapsedMilliseconds retryReason 的错误。 每次重试尝试失败后, 将递增 1,将更新以反映到目前为止重新连接所用的时间(以毫秒为单位),并且 将是导致上次重新连接尝试失败 previousRetryCount elapsedMilliseconds retryReason 的错误。

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

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();

或者,可以编写代码来手动重新连接客户端,如手动重新连接 中演示。

手动重新连接

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

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

connection.onclose(start);

实际实现将使用指数退让,或在放弃之前重试指定次数。

其他资源

作者:Rachel Appel

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

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

安装 SignalR 客户端包

SignalRJavaScript 客户端库以npm包的形式提供。 以下部分概述了安装客户端库的不同方式。

通过 npm 安装

如果使用的是 Visual Studio,请在根文件夹中的 " 包管理器控制台 " 中运行以下命令。 对于 Visual Studio Code,请从 集成终端 运行以下命令。

npm init -y
npm install @aspnet/signalr

npm 将包内容安装到 *node_modules \ @aspnet\signalr\dist\browser* 文件夹中。 在 wwwroot \ lib 文件夹下创建名为 signalr 的新文件夹。 将 signalr.js 文件复制到 wwwroot\lib\signalr 文件夹。

SignalR在元素中引用 JavaScript 客户端 <script> 。 例如:

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

使用内容交付网络 (CDN)

若要在不使用 npm 先决条件的情况下使用客户端库,请引用 CDN 托管的客户端库副本。 例如:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.3/signalr.min.js"></script>

以下 Cdn 提供了客户端库:

使用 LibMan 安装

LibMan 可用于从 CDN 托管的客户端库安装特定客户端库文件。 例如,仅将缩小的 JavaScript 文件添加到项目。 有关该方法的详细信息,请参阅 添加 SignalR 客户端库

连接到中心

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

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

async function start() {
    try {
        await connection.start();
        console.log("connected");
    } catch (err) {
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

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

// Start the connection.
start();

/* this is here to show an alternative to start, with a then
connection.start().then(() => console.log("connected"));
*/

/* this is here to show another alternative to start, with a catch
connection.start().catch(err => console.error(err));
*/

跨源连接

通常,浏览器从请求页面的同一个域加载连接。 但是,有时需要连接到另一个域。

为了防止恶意站点从另一站点读取 敏感数据,默认情况下 禁用跨源连接。 若要允许跨源请求,在 类中启用 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 客户端通过HubConnectioninvoke方法在中心调用公共方法。 方法 invoke 接受两个参数:

  • 中心方法的名称。 在下面的示例中,中心上的方法名称为 SendMessage

  • 中心方法中定义的任何参数。 在下面的示例中,参数名称为 message 。 示例代码使用所有主要浏览器的当前版本支持的箭头函数语法,Internet Explorer。

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

备注

仅 SignalR 在 默认 模式下使用 Azure 服务时,才支持从客户端调用中心方法。 有关详细信息,请参阅 Signalr GitHub 存储库) (常见问题解答

invoke方法返回 JavaScript承诺Promise当服务器上的方法返回时,将用返回值解析) (。 如果服务器上的方法引发错误,将拒绝, Promise 并出现错误消息。 使用 thencatch 方法 Promise 来处理这些事例 (或 await 语法) 。

send方法返回 JavaScript PromisePromise当消息已发送到服务器时,将解决。 如果发送消息时出错,将 Promise 拒绝,并出现错误消息。 使用 thencatch 方法 Promise 来处理这些事例 (或 await 语法) 。

备注

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

从中心调用客户端方法

若要从中心接收消息,请使用的 on 方法定义方法 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 通过匹配和中定义的方法名称和参数,确定要调用的客户端方法 SendAsync connection.on

备注

最佳做法是,在 之后调用 上的 HubConnection start 方法 on 。 这样做可确保在接收任何消息之前注册处理程序。

错误处理和日志记录

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

connection.start().catch(err => console.error(err));

通过传递记录器以及建立连接时要记录的事件类型来设置客户端日志跟踪。 使用指定的日志级别和更高级别记录消息。 可用的日志级别如下所示:

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

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

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

重新连接客户端

手动重新连接

警告

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

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

  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();
});

实际的实现将使用指数回退或在放弃之前重试指定的次数。

其他资源