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

作者:Rachel AppelBy Rachel Appel

ASP.NET Core SignalR JavaScript 客户端库使开发人员能够调用服务器端集线器代码。The ASP.NET Core SignalR JavaScript client library enables developers to call server-side hub code.

查看或下载示例代码如何下载View or download sample code (how to download)

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

SignalRJavaScript 客户端库以npm包的形式提供。The SignalR JavaScript client library is delivered as an npm package. 以下部分概述了安装客户端库的不同方式。The following sections outline different ways to install the client library.

通过 npm 安装Install with npm

如果使用的是 Visual Studio,请在根文件夹中的 "包管理器控制台" 中运行以下命令。If using Visual Studio, run the following commands from Package Manager Console while in the root folder. 对于 Visual Studio Code,请从集成终端运行以下命令。For Visual Studio Code, run the following commands from the Integrated Terminal.

npm init -y
npm install @microsoft/signalr

npm 将包内容安装到*node_modules \ @microsoft\signalr\dist\browser *文件夹中。npm installs the package contents in the node_modules\@microsoft\signalr\dist\browser folder. wwwroot \ lib文件夹下创建名为signalr的新文件夹。Create a new folder named signalr under the wwwroot\lib folder. signalr.js文件复制到wwwroot\lib\signalr文件夹。Copy the signalr.js file to the wwwroot\lib\signalr folder.

npm init -y
npm install @aspnet/signalr

npm 将包内容安装到*node_modules \ @aspnet\signalr\dist\browser *文件夹中。npm installs the package contents in the node_modules\@aspnet\signalr\dist\browser folder. wwwroot \ lib文件夹下创建名为signalr的新文件夹。Create a new folder named signalr under the wwwroot\lib folder. signalr.js文件复制到wwwroot\lib\signalr文件夹。Copy the signalr.js file to the wwwroot\lib\signalr folder.

SignalR在元素中引用 JavaScript 客户端 <script>Reference the SignalR JavaScript client in the <script> element. 例如:For example:

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

使用内容交付网络 (CDN) Use a Content Delivery Network (CDN)

若要在不使用 npm 先决条件的情况下使用客户端库,请引用 CDN 托管的客户端库副本。To use the client library without the npm prerequisite, reference a CDN-hosted copy of the client library. 例如:For example:

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

以下 Cdn 提供了客户端库:The client library is available on the following CDNs:

通过 LibMan 安装Install with LibMan

LibMan可用于从 CDN 托管的客户端库安装特定的客户端库文件。LibMan can be used to install specific client library files from the CDN-hosted client library. 例如,仅将缩小 JavaScript 文件添加到项目。For example, only add the minified JavaScript file to the project. 有关该方法的详细信息,请参阅添加 SignalR 客户端库For details on that approach, see Add the SignalR client library.

连接到集线器Connect to a hub

下面的代码创建并启动连接。The following code creates and starts a connection. 中心名称不区分大小写。The hub's name is case insensitive.

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));
*/

跨域连接Cross-origin connections

通常,浏览器从与请求的页相同的域中加载连接。Typically, browsers load connections from the same domain as the requested page. 但是,在某些情况下,需要与另一个域建立连接。However, there are occasions when a connection to another domain is required.

为了防止恶意站点读取其他站点中的敏感数据,默认情况下会禁用跨域连接To prevent a malicious site from reading sensitive data from another site, cross-origin connections are disabled by default. 若要允许跨源请求,请在类中启用它 StartupTo allow a cross-origin request, enable it in the Startup class.

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

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

JavaScript 客户端通过HubConnectioninvoke方法在集线器上调用公共方法。JavaScript clients call public methods on hubs via the invoke method of the HubConnection. invoke 方法接受两个参数:The invoke method accepts two arguments:

  • 集线器方法的名称。The name of the hub method. 在下面的示例中,中心的方法名称是 SendMessageIn the following example, the method name on the hub is SendMessage.

  • 在 hub 方法中定义的所有参数。Any arguments defined in the hub method. 在下面的示例中,自变量名称为 messageIn the following example, the argument name is message. 示例代码使用了在所有主要浏览器(Internet Explorer 除外)的当前版本中受支持的箭头函数语法。The example code uses arrow function syntax that is supported in current versions of all major browsers except Internet Explorer.

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

备注

如果 SignalR 在无服务器模式下使用 Azure 服务,则无法从客户端调用集线器方法。If you're using Azure SignalR Service in Serverless mode, you cannot call hub methods from a client. 有关详细信息,请参阅 SignalR 服务文档For more information, see the SignalR Service documentation.

invoke方法返回 JavaScript承诺The invoke method returns a JavaScript Promise. Promise当服务器上的方法返回时,将用返回值解析) (。The Promise is resolved with the return value (if any) when the method on the server returns. 如果服务器上的方法引发错误,将拒绝, Promise 并出现错误消息。If the method on the server throws an error, the Promise is rejected with the error message. 使用 thencatch 方法 Promise 来处理这些事例 (或 await 语法) 。Use the then and catch methods on the Promise itself to handle these cases (or await syntax).

send方法返回 JavaScript PromiseThe send method returns a JavaScript Promise. Promise当消息已发送到服务器时,将解决。The Promise is resolved when the message has been sent to the server. 如果发送消息时出错,将 Promise 拒绝,并出现错误消息。If there is an error sending the message, the Promise is rejected with the error message. 使用 thencatch 方法 Promise 来处理这些事例 (或 await 语法) 。Use the then and catch methods on the Promise itself to handle these cases (or await syntax).

备注

使用 send 不会等到服务器收到消息。Using send doesn't wait until the server has received the message. 因此,不可能从服务器返回数据或错误。Consequently, it's not possible to return data or errors from the server.

从中心调用客户端方法Call client methods from hub

若要从中心接收消息,请使用的on方法定义方法 HubConnectionTo receive messages from the hub, define a method using the on method of the HubConnection.

  • JavaScript 客户端方法的名称。The name of the JavaScript client method. 在下面的示例中,方法名称是 ReceiveMessageIn the following example, the method name is ReceiveMessage.
  • 集线器传递给方法的参数。Arguments the hub passes to the method. 在下面的示例中,参数值为 messageIn the following example, the argument value is 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方法调用时,上面的代码将运行。The preceding code in connection.on runs when server-side code calls it using the SendAsync method.

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

SignalR通过匹配和中定义的方法名称和参数,确定要调用的客户端方法 SendAsync connection.onSignalR determines which client method to call by matching the method name and arguments defined in SendAsync and connection.on.

备注

作为最佳做法,请在后面调用start方法 HubConnection onAs a best practice, call the start method on the HubConnection after on. 这样做可确保在收到消息之前注册处理程序。Doing so ensures your handlers are registered before any messages are received.

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

catch 方法链接到方法的末尾 start ,以处理客户端错误。Chain a catch method to the end of the start method to handle client-side errors. 使用 console.error 将错误输出到浏览器控制台。Use console.error to output errors to the browser's console.

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

设置客户端日志跟踪,方法是在建立连接时将记录器和事件类型传递给日志。Set up client-side log tracing by passing a logger and type of event to log when the connection is made. 记录的消息具有指定的日志级别和更高的日志级别。Messages are logged with the specified log level and higher. 可用的日志级别如下所示:Available log levels are as follows:

  • signalR.LogLevel.Error:错误消息。signalR.LogLevel.Error: Error messages. Error仅记录消息。Logs Error messages only.
  • signalR.LogLevel.Warning:有关潜在错误的警告消息。signalR.LogLevel.Warning: Warning messages about potential errors. 日志 WarningError 消息。Logs Warning, and Error messages.
  • signalR.LogLevel.Information:无错误的状态消息。signalR.LogLevel.Information: Status messages without errors. 日志 InformationWarningError 消息。Logs Information, Warning, and Error messages.
  • signalR.LogLevel.Trace:跟踪消息。signalR.LogLevel.Trace: Trace messages. 记录所有内容,包括中心和客户端之间传输的数据。Logs everything, including data transported between hub and client.

使用HubConnectionBuilder上的configureLogging方法配置日志级别。Use the configureLogging method on HubConnectionBuilder to configure the log level. 消息将记录到浏览器控制台。Messages are logged to the browser console.

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

重新连接客户端Reconnect clients

自动重新连接Automatically reconnect

的 JavaScript 客户端 SignalR 可以配置为使用 withAutomaticReconnect HubConnectionBuilder上的方法自动重新连接。The JavaScript client for SignalR can be configured to automatically reconnect using the withAutomaticReconnect method on HubConnectionBuilder. 默认情况下,它不会自动重新连接。It won't automatically reconnect by default.

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

在没有任何参数的情况下,会 withAutomaticReconnect() 将客户端配置为分别等待0、2、10和30秒,然后再尝试重新连接尝试。Without any parameters, withAutomaticReconnect() configures the client to wait 0, 2, 10, and 30 seconds respectively before trying each reconnect attempt, stopping after four failed attempts.

在开始任何重新连接尝试之前, HubConnection 将转换为 HubConnectionState.Reconnecting 状态,并激发其 onreconnecting 回调,而不是转换为 Disconnected 状态,并触发其 onclose 回调,如 HubConnection 不配置自动重新连接。Before starting any reconnect attempts, the HubConnection will transition to the HubConnectionState.Reconnecting state and fire its onreconnecting callbacks instead of transitioning to the Disconnected state and triggering its onclose callbacks like a HubConnection without automatic reconnect configured. 这为用户提供警告连接已丢失并禁用 UI 元素的机会。This provides an opportunity to warn users that the connection has been lost and to disable UI elements.

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 回调。If the client successfully reconnects within its first four attempts, the HubConnection will transition back to the Connected state and fire its onreconnected callbacks. 这为用户提供了通知用户连接已重新建立的机会。This provides an opportunity to inform users the connection has been reestablished.

由于连接在服务器上看起来是全新的,因此 connectionId 将向回调提供一个新的 onreconnectedSince the connection looks entirely new to the server, a new connectionId will be provided to the onreconnected callback.

警告

onreconnected connectionId 如果 HubConnection 配置为跳过协商,则不会定义回调的参数。The onreconnected callback's connectionId parameter will be undefined if the HubConnection was configured to skip negotiation.

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 为重试初始启动失败,因此,需要手动处理启动失败:withAutomaticReconnect() won't configure the HubConnection to retry initial start failures, so start failures need to be handled manually:

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回调。If the client doesn't successfully reconnect within its first four attempts, the HubConnection will transition to the Disconnected state and fire its onclose callbacks. 这为用户提供了通知用户连接永久丢失的机会,并建议刷新页面:This provides an opportunity to inform users the connection has been permanently lost and recommend refreshing the page:

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 接受一个数字数组,表示在开始每次重新连接尝试之前等待的延迟(以毫秒为单位)。In order to configure a custom number of reconnect attempts before disconnecting or change the reconnect timing, withAutomaticReconnect accepts an array of numbers representing the delay in milliseconds to wait before starting each reconnect attempt.

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

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

前面的示例将配置 HubConnection 为在连接丢失后立即开始尝试重新连接。The preceding example configures the HubConnection to start attempting reconnects immediately after the connection is lost. 这也适用于默认配置。This is also true for the default configuration.

如果第一次重新连接尝试失败,则第二次重新连接尝试还会立即启动,而不是等待2秒,就像在默认配置中一样。If the first reconnect attempt fails, the second reconnect attempt will also start immediately instead of waiting 2 seconds like it would in the default configuration.

如果第二次重新连接尝试失败,则第三次重新连接尝试将在10秒内启动,这与默认配置相同。If the second reconnect attempt fails, the third reconnect attempt will start in 10 seconds which is again like the default configuration.

然后,在第三次重新连接尝试失败后,自定义行为将再次从默认行为与其分离,而不是在另一个30秒内尝试再次尝试重新连接,就像在默认配置中一样。The custom behavior then diverges again from the default behavior by stopping after the third reconnect attempt failure instead of trying one more reconnect attempt in another 30 seconds like it would in the default configuration.

如果需要更好地控制计时和自动重新连接尝试的次数,则 withAutomaticReconnect 接受一个实现接口的 IRetryPolicy 对象,该对象具有一个名为的方法 nextRetryDelayInMillisecondsIf you want even more control over the timing and number of automatic reconnect attempts, withAutomaticReconnect accepts an object implementing the IRetryPolicy interface, which has a single method named nextRetryDelayInMilliseconds.

nextRetryDelayInMilliseconds采用类型为的单个自变量 RetryContextnextRetryDelayInMilliseconds takes a single argument with the type RetryContext. RetryContext具有三个属性: previousRetryCount elapsedMilliseconds 和分别为 retryReason numbernumberErrorThe RetryContext has three properties: previousRetryCount, elapsedMilliseconds and retryReason which are a number, a number and an Error respectively. 第一次重新连接尝试之前, previousRetryCount 和都 elapsedMilliseconds 是零, retryReason 将是导致连接丢失的错误。Before the first reconnect attempt, both previousRetryCount and elapsedMilliseconds will be zero, and the retryReason will be the Error that caused the connection to be lost. 每次失败的重试次数递增一次后,将进行 previousRetryCount elapsedMilliseconds 更新,以反映到目前为止的重新连接所用的时间(以毫秒为单位),并且 retryReason 将是导致上次重新连接尝试失败的错误。After each failed retry attempt, previousRetryCount will be incremented by one, elapsedMilliseconds will be updated to reflect the amount of time spent reconnecting so far in milliseconds, and the retryReason will be the Error that caused the last reconnect attempt to fail.

nextRetryDelayInMilliseconds必须返回一个数字,该数字表示在下一次重新连接尝试之前要等待的毫秒数,或者 null ,如果应停止重新连接,则为 HubConnectionnextRetryDelayInMilliseconds must return either a number representing the number of milliseconds to wait before the next reconnect attempt or null if the HubConnection should stop reconnecting.

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

或者,你可以编写将手动重新连接客户端的代码,如手动重新连接中所示。Alternatively, you can write code that will reconnect your client manually as demonstrated in Manually reconnect.

手动重新连接Manually reconnect

警告

在3.0 之前, SignalR 不会自动重新连接 JavaScript 客户端。Prior to 3.0, the JavaScript client for SignalR doesn't automatically reconnect. 必须编写代码来手动重新连接客户端。You must write code that will reconnect your client manually.

下面的代码演示典型的手动重新连接方法:The following code demonstrates a typical manual reconnection approach:

  1. 函数 (在这种情况下,将 start 创建函数) 来启动连接。A function (in this case, the start function) is created to start the connection.
  2. start在连接的 onclose 事件处理程序中调用函数。Call the start function in the connection's onclose event handler.
async function start() {
    try {
        await connection.start();
        console.log("connected");
    } catch (err) {
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

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

实际的实现将使用指数回退或在放弃之前重试指定的次数。A real-world implementation would use an exponential back-off or retry a specified number of times before giving up.

其他资源Additional resources