ASP.NET Core SignalR JavaScript 客户端ASP.NET Core SignalR JavaScript client

本文内容

作者:Rachel Appel

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

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

安装 SignalR 客户端包Install the SignalR client package

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

  1. npm init -y
  2. npm install @microsoft/signalr

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

  1. npm init -y
  2. npm install @aspnet/signalr

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

使用 SignalR JavaScript 客户端Use the SignalR JavaScript client

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

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

连接到中心Connect to a hub

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

  1. const connection = new signalR.HubConnectionBuilder()
  2. .withUrl("/chatHub")
  3. .configureLogging(signalR.LogLevel.Information)
  4. .build();
  5. start();
  6. /* this is here to show an alternative to start, with a then

跨域的连接Cross-origin connections

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

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

  1. using Microsoft.AspNetCore.Builder;
  2. using Microsoft.AspNetCore.Hosting;
  3. using Microsoft.AspNetCore.Http;
  4. using Microsoft.Extensions.Configuration;
  5. using Microsoft.Extensions.DependencyInjection;
  6. using SignalRChat.Hubs;
  7. namespace SignalRChat
  8. {
  9. public class Startup
  10. {
  11. public Startup(IConfiguration configuration)
  12. {
  13. Configuration = configuration;
  14. }
  15. public IConfiguration Configuration { get; }
  16. public void ConfigureServices(IServiceCollection services)
  17. {
  18. services.Configure<CookiePolicyOptions>(options =>
  19. {
  20. options.CheckConsentNeeded = context => true;
  21. options.MinimumSameSitePolicy = SameSiteMode.None;
  22. });
  23. services.AddMvc();
  24. services.AddCors(options => options.AddPolicy("CorsPolicy",
  25. builder =>
  26. {
  27. builder.AllowAnyMethod().AllowAnyHeader()
  28. .WithOrigins("http://localhost:55830")
  29. .AllowCredentials();
  30. }));
  31. services.AddSignalR();
  32. }
  33. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  34. {
  35. if (env.IsDevelopment())
  36. {
  37. app.UseBrowserLink();
  38. app.UseDeveloperExceptionPage();
  39. }
  40. else
  41. {
  42. app.UseExceptionHandler("/Error");
  43. app.UseHsts();
  44. }
  45. app.UseHttpsRedirection();
  46. app.UseStaticFiles();
  47. app.UseCookiePolicy();
  48. app.UseCors("CorsPolicy");
  49. app.UseSignalR(routes =>
  50. {
  51. routes.MapHub<ChatHub>("/chathub");
  52. });
  53. app.UseMvc();
  54. }
  55. }
  56. }

从客户端调用集线器方法Call hub methods from client

JavaScript 客户端通过HubConnectioninvoke方法在集线器上调用公共方法。invoke 方法接受两个参数:

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

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

  1. 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 不会等到服务器收到消息。因此,不可能从服务器返回数据或错误。

从集线器调用客户端方法Call client methods from hub

若要从中心接收消息,请使用 HubConnectionon方法定义方法。

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

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

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

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

备注

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

错误处理和日志记录Error handling and logging

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

  1. */
  2. /* this is here to show another alternative to start, with a catch

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

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

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

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

重新连接客户端Reconnect clients

自动重新连接Automatically reconnect

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

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

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

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

  1. connection.onreconnecting((error) => {
  2. console.assert(connection.state === signalR.HubConnectionState.Reconnecting);
  3. document.getElementById("messageInput").disabled = true;
  4. const li = document.createElement("li");
  5. li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
  6. document.getElementById("messagesList").appendChild(li);
  7. });

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

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

警告

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

  1. connection.onreconnected((connectionId) => {
  2. console.assert(connection.state === signalR.HubConnectionState.Connected);
  3. document.getElementById("messageInput").disabled = false;
  4. const li = document.createElement("li");
  5. li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
  6. document.getElementById("messagesList").appendChild(li);
  7. });

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

  1. async function start() {
  2. try {
  3. await connection.start();
  4. console.assert(connection.state === signalR.HubConnectionState.Connected);
  5. console.log("connected");
  6. } catch (err) {
  7. console.assert(connection.state === signalR.HubConnectionState.Disconnected);
  8. console.log(err);
  9. setTimeout(() => start(), 5000);
  10. }
  11. };

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

  1. connection.onclose((error) => {
  2. console.assert(connection.state === signalR.HubConnectionState.Disconnected);
  3. document.getElementById("messageInput").disabled = true;
  4. const li = document.createElement("li");
  5. li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
  6. document.getElementById("messagesList").appendChild(li);
  7. });

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

  1. const connection = new signalR.HubConnectionBuilder()
  2. .withUrl("/chatHub")
  3. .withAutomaticReconnect([0, 0, 10000])
  4. .build();
  5. // .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

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

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

手动重新连接Manually reconnect

警告

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

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

  • 创建函数(在本例中为 start 函数)以启动连接。
  • 在连接的 onclose 事件处理程序中调用 start 函数。
  1. async function start() {
  2. try {
  3. await connection.start();
  4. console.log("connected");
  5. } catch (err) {
  6. console.log(err);
  7. setTimeout(() => start(), 5000);
  8. }
  9. };
  10. connection.onclose(async () => {
  11. await start();
  12. });

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

其他资源Additional resources