ASP.NET Core SignalRJavaScript 客户端

作者: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(async () => {
    await 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 方法接受:

  • 集线器方法的名称。
  • 在 hub 方法中定义的所有参数。

在下面的示例中,中心的方法名称是 SendMessage 。 传递给 invoke 集线器方法和参数的第二个和第三个参数 user message

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

备注

仅 SignalR 在 默认 模式下使用 Azure 服务时,才支持从客户端调用中心方法。 有关详细信息,请参阅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

备注

作为最佳做法,请在后面调用 start 方法 HubConnection on 。 这样做可确保在收到消息之前注册处理程序。

错误处理和日志记录

使用和,使用 try catch asyncawaitPromisecatch 方法来处理客户端错误。 使用 console.error 将错误输出到浏览器控制台:

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

设置客户端日志跟踪,方法是在建立连接时将记录器和事件类型传递给日志。 记录的消息具有指定的日志级别和更高的日志级别。 可用的日志级别如下所示:

  • signalR.LogLevel.Error:错误消息。 Error仅记录消息。
  • signalR.LogLevel.Warning:有关潜在错误的警告消息。 日志 WarningError 消息。
  • signalR.LogLevel.Information:无错误的状态消息。 日志 InformationWarningError 消息。
  • 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(async () => {
    await start();
});

生产实现通常使用指数退市或重试指定次数。

浏览器睡眠选项卡

某些浏览器具有选项卡冻结或休眠功能,以减少非活动选项卡的计算机资源使用量。 这可能会导致 SignalR 连接关闭,并可能导致不需要的用户体验。 浏览器使用启发法来找出选项卡是否应进入睡眠状态,例如:

  • 播放音频
  • 持有 Web 锁
  • 持有 IndexedDB
  • 连接到 USB 设备
  • 捕获视频或音频
  • 正在镜像
  • 捕获窗口或显示

备注

这些启发方法可能会随着时间的推移而改变,或者因浏览器不同而不同。 查看支持矩阵,确定哪种方法最适合你的方案。

为了避免使应用进入睡眠状态,应用应触发浏览器使用的启发式方法之一。

下面的代码示例演示如何使用 Web 锁 使选项卡保持唤醒状态并避免意外的连接关闭。

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

对于前面的代码示例:

  • Web 锁是实验性的。 条件检查确认浏览器支持 Web 锁。
  • 将存储 () 解析程序,以便可以在选项卡进入睡眠状态时 lockResolver 释放锁。
  • 关闭连接时,通过调用 释放锁 lockResolver() 。 释放锁后,允许选项卡进入睡眠状态。

其他资源

作者:Rachel Appel

开发人员 ASP.NET Core JavaScript 客户端库 SignalR 调用服务器端中心代码。

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

安装 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));
    

备注

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

方法 invoke 返回 JavaScript Promise。 如果 Promise 在服务器上的 方法返回 (返回) ,则使用返回值进行解析。 如果服务器上 的方法引发错误,则 Promise 拒绝 并返回错误消息。 使用 then catch 本身上的 和 Promise 方法来处理这些情况 (await 语法) 。

方法 send 返回 JavaScript PromisePromise当消息已发送到服务器时,将解析 。 如果发送消息时出错,将 Promise 拒绝 并返回错误消息。 使用 then catch 本身上的 和 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();
});

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

其他资源