发布于 2026-01-05 1 阅读
0

如何学习使用 .NET Core、C#、Azure SignalR 服务和 JavaScript 构建可扩展的实时 Web 应用程序

如何学习使用 .NET Core、C#、Azure SignalR 服务和 JavaScript 构建可扩展的实时 Web 应用程序

欢迎在推特上关注我,我很乐意接受您对话题或改进方面的建议。/克里斯

好的,所以你想构建实时应用程序?你需要考虑一些问题,比如如果浏览器不支持 WebSocket 怎么办?我的备用技术是什么?还有,如何扩展?CORS 又该如何处理?正如你所见,这远不止创建一个 WebSocket 连接那么简单。这就是 SignalR 存在的意义,它正是为了解决上述问题而生的。

简而言之,本文将探讨两个方面:一是 SignalR 本身,包括它的定义和用途;二是 Azure SignalR 服务,并讨论其与 Azure SignalR 服务的区别;三是演示如何使用 SignalR 服务和 Serverless 架构。

本文是#25DaysOfServerless活动的一部分。整个十二月,微软云技术倡导者每天都会发布新的挑战。了解更多关于 Microsoft Azure 如何助力您实现无服务器功能的信息

参考


  • 要使用 Azure SignalR 服务,您需要一个免费的 Azure 帐户

  • SignalR 概述:
    一个很棒的页面,解释了 SignalR 是什么、它是如何工作的等等。

  • ASP.NET Core SignalR
    概述很棒。虽然不像第一页那样细节丰富,但仍然很好地涵盖了基本概念,可以算是一个简明扼要的版本。

  • SignalR GitHub 仓库
    是开源的,其中包含使用不同语言编写的 Serverless 部分示例,以及带身份验证和不带身份验证的客户端示例。

  • SignalR + .NET Core 教程
    本教程涵盖如何在 .NET Core Web 项目中构建 SignalR 后端,以及如何从客户端调用它。

SignalR

ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,它简化了向应用程序添加实时 Web 功能的过程。实时 Web 功能是指服务器端代码能够在内容可用时立即将其送给已连接的客户端,而无需服务器等待客户端请求新数据。

我可以用它来做什么?

虽然聊天经常被用作一个例子,但你还可以做更多的事情,例如仪表板和监控应用程序、协作应用程序(例如同时编辑文档)、工作进度更新和实时表单。

我如何判断何时应该使用 SignalR?

每当用户刷新网页以查看新数据,或者网页实现长轮询以检索新数据时,它都是使用 SignalR 的候选对象。

它是如何运作的?

SignalR 提供了一个简单的 API,用于创建服务器到客户端的远程过程调用 (RPC),该调用可以从服务器端 .NET 代码调用客户端浏览器(和其他客户端平台)中的 JavaScript 函数。

好的,所以 SignalR 会在有新数据时调用我的 JavaScript 代码吗?

正确的。

SignalR 可自动处理连接管理,并允许您像聊天室一样同时向所有已连接的客户端广播消息。您还可以向特定客户端发送消息。

可以广播,也可以针对特定客户,明白了。

SignalR 会尽可能使用新的 WebSocket 传输方式,并在必要时回退到旧版传输方式。虽然您完全可以直接使用 WebSocket 编写应用程序,但使用 SignalR 意味着许多您需要实现的额外功能都已经为您准备就绪。

哦,所以我可以用 WebSocket 代替 SignalR,但如果不行的话,我就得自己写个备用方案。但如果我用 SignalR,就不用担心这个问题了吗?我主要用的是 WebSocket,但也有备用方案?

正确的。

主机

SignalR 的托管方式有两种:

  • SignalR采用自托管模式,我们将其作为 Web 应用程序的一部分自行托管。
  • Azure SignalR 服务,即以服务形式存在于云端的 SignalR,它具有诸多优势。

以下是概述:

 Azure SignalR 服务

为什么我应该选择使用该服务而不是自托管?

切换到 SignalR 服务将消除管理处理秤和客户端连接的背板的需要。

好的,所以你负责处理客户端连接和扩展。不错,我喜欢这个方案。

完全托管服务还能简化 Web 应用程序并节省托管成本。

简化流程,还能帮我省钱。我没意见 :)

SignalR 服务提供全球覆盖范围和世界一流的数据中心和网络,可扩展至数百万个连接,保证 SLA,同时提供 Azure 标准的所有合规性和安全性。

数百万个连接!这可真是一个大型聊天室 ;) 服务级别协议合规,这让我的CEO和法务部门非常满意 :) 当然,良好的安全性是必不可少的。

如何

我知道你想学如何使用这个,那我们开始吧?我们会的:

  • 预配Azure SignalR 服务
  • 创建一个 Azure 函数应用,以便连接到 Azure SignalR 服务。我们将学习如何管理连接,以及如何接收和发送消息。
  • 创建一个能够连接到我们的 Azure 函数应用并发送/接收消息的用户界面。

预配 Azure SignalR 服务

  1. 前往portal.azure.com

  2. 点击+ Create a resource

  3. SignalR Service在搜索框中输入

  1. 按下Review + Create,然后Create在下一个屏幕上继续。

注意,还有最后一步。我们需要配置 Azure SignalR 服务,使其能够与无服务器应用程序通信,否则连接时的握手将会失败。我可是吃过亏的:)

创建 Azure 函数应用

这需要我们创建一个 Azure 函数应用。它将包含两个不同的函数:

  • 协商过程会与我们的 Azure SignalR 服务通信,并返回一个 API 密钥,我们可以使用该密钥来执行诸如发送消息之类的操作。
  • 此端点将用于发送消息

先决条件

首先,与所有 Azure 函数一样,我们需要确保已安装必要的先决条件,这些先决条件在不同的操作系统上有所不同:

Mac 版:



brew tap azure/functions
brew install azure-functions-core-tools


Enter fullscreen mode Exit fullscreen mode

适用于 Windows 系统:



npm install -g azure-functions-core-tools


Enter fullscreen mode Exit fullscreen mode

如果您使用的是Linux操作系统,请点击此处阅读更多内容:

https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local

还有一点,为了方便编写无服务器函数,我建议安装 Azure 函数扩展。这将使您能够创建函数框架,并进行调试和部署。请在 VS Code 中转到扩展选项卡并安装以下扩展:

如果您使用的是 Visual Studio,请查看这里:

https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-vs

创建我们的无服务器函数

好的,那么,为了本文的演示,我们将选择 VS Code 作为我们的集成开发环境 (IDE)。我们将执行以下操作:

  • 创建 Azure 函数应用,Azure 函数必须属于一个应用。
  • 搭建两个 Azure 函数negotiate并且messages
  • 配置我们的两个函数以与 Azure SignalR 服务协同工作

打开命令面板View/Command Palette,或者CMD+SHIFT+P在 Mac 上打开。

接下来,为你的应用选择一个目录(我通常会选择我当前所在的目录)。

之后,我们需要选择一种语言。如下所示,我们有很多选项。这次就选英语吧C#

下一步是Trigger为您的第一个函数选择一个启动方式(首次创建 Serverless 项目时,系统会创建一个项目和一个函数)。Trigger启动方式决定了函数的启动条件。​​在本例中,我们希望它通过 HTTP 调用启动/触发,因此我们选择HttpTrigger以下方式:

这里还有两个步骤,分别是:

  • 我们函数的名称,就叫它吧。negotiate
  • 命名空间,就叫它Company
  • 授权,我们开始吧Anonymous

好了,现在我们已经创建了一个 Serverless .NET Core 项目。让我们再次打开命令面板View/Command Palette,然后输入Azure Functions: Create Function如下命令。

选择:

  • 触发器选择HttpTrigger
  • 函数名,调用它messages
  • 命名空间称之为Company
  • 授权级别,我们来选择anonymous

好的,那么现在我们应该创建一个包含两个函数的函数应用/函数项目。重命名后,它应该看起来negotiate.cs这样Negotiate.csmessages.csMessages.cs

配置 SignalR

此时我们需要做两件事:

  • 在代码中添加 SignalR 装饰器,这可以确保我们连接到云端正确的 Azure SignalR 实例。
  • 添加连接字符串信息,我们需要将此信息添加到配置文件中,以便它知道要与哪个 SignalR 实例通信。

添加 SignalR 装饰器

让我们打开它Negotiate.cs,并输入以下代码:



// Negotiate.cs
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;

namespace Company
{
  public static class Negotiate
  {
      [FunctionName("negotiate")]
      public static SignalRConnectionInfo GetSignalRInfo(
          [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
          [SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
      {
          return connectionInfo;
      }
  }
}


Enter fullscreen mode Exit fullscreen mode

从上面的代码可以看出,我们使用了装饰器SignalRConnectionInfo,并指向了一个名为 `<hub>` 的所谓中心chat。此外,我们还可以看到该函数最终返回了一个connectionInfo对象。这里发生的情况是,当 HTTP 请求访问此端点时,我们会与云端的 Azure SignalR 服务进行握手,并最终获得所需的连接信息,以便我们在执行诸如发送消息之类的操作时能够继续与其通信。

现在让我们打开Messages.cs它,并输入以下代码:



// Messages.cs
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;

namespace Company
{
  public static class Messages
  {

    [FunctionName("messages")]
    public static Task SendMessage(
          [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
          [SignalR(HubName = "chat")] IAsyncCollector<SignalRMessage> signalRMessages)
    {
      return signalRMessages.AddAsync(
          new SignalRMessage
          {
            Target = "newMessage",
            Arguments = new[] { message }
          });
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

这次我们也使用了一个装饰器,但它的名称是…… SignalR,但我们仍然给它赋予 Hub 值chat。我们的 SignalR 装饰器装饰了一个消息列表,该列表具有参数名称signalRMessages

接下来我们来看一下函数体。我们看到我们调用了 `.` signalRMessages.AddAsync()。它的作用是什么呢?它传入了SignalRMessage一个包含两部分的内容:

  • Target,这是一个事件的名称,在本例中为“” newMessage。客户端可以监听此事件并渲染其有效负载,例如。
  • 参数,这只是有效载荷,在这种情况下,我们只想广播来自一个客户端的所有消息,以确保其他监听客户端能够及时了解有新数据。

添加连接字符串

好的,我们了解到代码需要 SignalR 装饰器才能正常工作。但是,除非我们将连接字符串信息添加到名为local.setting.json.的项目配置文件中,否则一切都无法正常工作。

我们来看一下文件的当前状态:



{
    "IsEncrypted": false,
    "Values": {
        "FUNCTIONS_WORKER_RUNTIME": "dotnet",
        "AzureSignalRConnectionString": "<add connection string info here>"
    },
    "Host": {
        "LocalHttpPort": 7071,
        "CORS": "<add allowed client domains here>",
        "CORSCredentials": true
    }
}


Enter fullscreen mode Exit fullscreen mode

我们来看一下AzureSignalRConnectionString,这里需要正确的连接字符串信息。我们可以访问云端的 Azure SignalR 服务来找到这些信息。

  1. 前往portal.azure.com
  2. 选择您的 Azure SignalR 服务
  3. 点击keys左侧菜单
  4. 复制下面的值CONNECTION STRING

接下来,我们来更新CORS属性。因为我们是在本地运行,所以目前我们需要允许http://localhost:8080与我们的 Azure 函数应用和 Azure SignalR 服务通信的进程。

注意,我们将确保即将创建的客户端在端口上运行8080

创建用户界面

好了,我们已经完成了创建后端和 Azure SignalR 服务的所有必要步骤,该服务能够扩展我们的实时连接。我们还添加了一个无服务器函数,它可以代理所有发送到 Azure SignalR 服务的调用。剩下的就是应用程序代码了,也就是用户将要看到的部分。

我们将开发一款聊天应用程序。因此,我们的应用程序将能够实现以下功能:

  • 建立与我们的 Azure SignalR 服务的连接
  • 显示来自其他客户端的传入消息
  • 向其他客户发送消息

建立联系

让我们选择一个与无服务器应用程序不同的目录。现在创建一个文件index.html,并添加以下内容:



<html>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.2/dist/browser/signalr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
    <script>

    </script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

上面我们添加了一些脚本标签:

  • Vue.js,这是一个指向 Vue.js CDN 版本的链接,你可以选择任何你想要的 SPA 框架,或者使用原生 JavaScript。
  • SignalR,这是 SignalR CDN 版本的链接,必不可少。我们需要它来建立与 SignalR Hub 的连接,并发送其他客户端可以监听的消息。
  • Axios,这是 Axios 的 CDN 版本链接,Axios 是一个用于处理 HTTP 请求的库。在这种情况下,使用原生 fetch 也可以,这取决于你。

如何在代码中建立连接?下面的代码就能做到这一点。我们指向apiBaseUrl已启动并运行的无服务器函数应用的所在位置。



const apiBaseUrl = 'http://localhost:7071';

const connection = new signalR.HubConnectionBuilder()
    .withUrl(`${apiBaseUrl}/api`)
    .configureLogging(signalR.LogLevel.Information)
    .build();


Enter fullscreen mode Exit fullscreen mode

上述操作将创建一个连接对象。要真正建立连接,我们需要调用start()该连接对象。



console.log('connecting...');
connection.start()
  .then((response) => {
    console.log('connection established', response);
  })
  .catch(logError);


Enter fullscreen mode Exit fullscreen mode

在继续之前,让我们先验证一下我们是否可以连接到我们的无服务器函数和 Azure SignalR 服务。

试驾一下

我们需要采取以下步骤进行测试:

  1. 以调试模式启动我们的无服务器函数
  2. 启动我们的客户http://localhost:8080
  3. 确保消息connection established显示在客户端上。

打开我们的 Serverless 应用,然后Debug/Start Debugging从菜单中选择。界面应该如下图所示。

另外,在函数的第一行设置一个断点Negotiate.cs,以便我们可以捕获客户端尝试连接时的情况。

接下来,让我们启动客户端http://localhost:8080。例如,http-server在客户端代码的根目录下使用以下命令:

http-server -p 8080

当你打开浏览器访问http://localhost:8080该页面时,应该会立即调用你的 Serverless 函数negotiate,如下所示:

如上所示,Azure SignalR 服务正在发送回您连接的AccessToken服务器。URL

查看浏览器,我们应该看到类似这样的内容:

很好,目前一切正常。这部分最难。接下来就是把它开发成用户想用的应用了。:)

 构建我们的 Vue.js 应用

我们的应用应该支持:

  • 我们已经成功连接到 Azure SignalR 服务。
  • 显示消息,能够显示来自其他客户端的消息
  • 发送消息,用户应该能够发送消息

开始工作吧 :)

创建一个 Vue.js 应用

我们需要创建一个 Vue 应用,并确保它渲染在特定的 DOM 元素上,如下所示:



<html>
  <body>
    <div id="app">
      App goes here
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.2/dist/browser/signalr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
    <script>
      const app = new Vue({
        el: '#app',    
      });

      const apiBaseUrl = 'http://localhost:7071';

      const connection = new signalR.HubConnectionBuilder()
        .withUrl(`${apiBaseUrl}/api`)
        .configureLogging(signalR.LogLevel.Information)
        .build();

      console.log('connecting...');
      connection.start()
        .then((response) => {
          console.log('connection established', response);
      })
        .catch(logError);


    </script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

以上是目前为止的全部代码。我们重点来看以下几点:



<div id ="app">
</div>


Enter fullscreen mode Exit fullscreen mode



const app = new Vue({
  el: '#app',    
});


Enter fullscreen mode Exit fullscreen mode

现在我们有了应用程序,但它什么也做不了。

显示消息

为了能够显示消息,我们需要监听从 Serverless 函数触发的事件。如果您还记得,我们在 Serverless 函数中调用了以下代码Messages.cs



return signalRMessages.AddAsync(
  new SignalRMessage
  {
    Target = "newMessage",
    Arguments = new[] { message }
  });


Enter fullscreen mode Exit fullscreen mode

我们想要监听newMessage上述函数触发的事件。相关代码如下:



connection.on('newMessage', newMessage);

function newMessage(message) {
  // do something with an incoming message
}


Enter fullscreen mode Exit fullscreen mode

好的,我该如何让这条消息显示在我的 Vue.js 应用中呢?

让我们确保将标记更新为这样:



<div id="app">
  <h2>Messages</h2>
  <div v-for="message in messages">
    <strong>{{message.sender}}</strong> {{message.text}}
  </div>
</div>


Enter fullscreen mode Exit fullscreen mode

以及我们的应用程序代码:



const data = {
  messages: []
}

const app = new Vue({
  el: '#app',    
  data: data
});


Enter fullscreen mode Exit fullscreen mode

还有这个:



function newMessage(message) {
  data.messages = [...data.messages, {...message}]
}


Enter fullscreen mode Exit fullscreen mode

现在我们可以渲染所有消息了。

但是聊天室里没有任何消息显示,而且我是唯一的用户,但我无法发送消息?

说得有道理,那就给你这个能力吧:

发送消息

我们需要一种方法让用户能够用 HTML 输入消息,还需要一种方法用代码将该消息发送到 SignalR Hub。我们先从 HTML 开始。



<div>
  <input type="text" v-model="newMessage" id="message-box" class="form-control"
    placeholder="Type message here..." autocomplete="off" />
  <button @click="sendMessage">Send message</button>
</div>


Enter fullscreen mode Exit fullscreen mode

以及发送函数的代码



function createMessage(sender, messageText) {
  return axios.post(`${apiBaseUrl}/api/messages`, {
    sender: sender,
    text: messageText
  }).then(resp => console.log('success sending message',resp.data);
}


Enter fullscreen mode Exit fullscreen mode

我们目前为止的完整代码如下所示:



<html>
  <body>
    <div id="app">
      <h2>
        User
      </h2>
      <div>
        <input type="text" v-model="user" placeholder="user name" />
      </div>
      <div>
          <input type="text" v-model="newMessage" id="message-box" class="form-control"
            placeholder="Type message here..." autocomplete="off" />
        <button @click="sendMessage">Send message</button>
      </div>
      <h2>Messages</h2>
      <div v-for="message in messages">
        <strong>{{message.sender}}</strong> {{message.text}}
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.2/dist/browser/signalr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
    <script>
      const data = {
        user: 'change me',
        messages: [],
        newMessage: ''
      }

      const app = new Vue({
        el: '#app',    
        data: data,
        methods: {
          sendMessage() {
            createMessage(this.user, this.newMessage);
          }
        }
      });

      const apiBaseUrl = 'http://localhost:7071';

      const connection = new signalR.HubConnectionBuilder()
        .withUrl(`${apiBaseUrl}/api`)
        .configureLogging(signalR.LogLevel.Information)
        .build();

      console.log('connecting...');
      connection.start()
        .then((response) => {
          console.log('connection established', response);
      })
        .catch(logError);

      connection.on('newMessage', newMessage);

      function newMessage(message) {
        data.messages = [...data.messages, {...message}]
      }

      function logError(err) {
        console.error('Error establishing connection', err);
      }

      function createMessage(sender, messageText) {
        return axios.post(`${apiBaseUrl}/api/messages`, {
          sender: sender,
          text: messageText
        }).then(resp => {
          console.log('message sent', resp);
        });
      }

    </script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

在两个不同的窗口中并排运行此程序,界面应该如下所示:

如您所见,它运行良好,但不够美观,因此您可以随意添加 Bootstrap、Bulma、动画或其他您认为必要的任何内容,使其成为一个优秀的应用程序。

概括

我们了解到以下几点:

  • SignalR是什么?它如何托管?是作为 Web 应用的一部分部署在应用服务中,还是通过 Azure SignalR 服务 + 无服务器架构?
  • 我们已经迈出了无服务器架构的第一步,并学习了如何使用函数搭建应用程序。
  • 在聊天功能部分,我们学习了如何通过创建无服务器应用程序作为端点来构建聊天功能,并且我们还使用 Vue.js 构建了一个客户端。

你想提交你对这个挑战的解决方案吗?


想提交你的解决方案吗?请在本地构建解决方案,然后提交一个 issue。如果你的解决方案不涉及代码,你可以录制一段简短的视频,并在 issue 描述中附上链接。请务必告诉我们你的解决方案是针对哪个挑战的。我们非常期待看到你的作品!你有任何意见或问题吗?请在下方评论区留言。


整个十二月,我们将举办为期 25 天的无服务器架构挑战赛,敬请期待!请持续关注 dev.to,我们将在此发布挑战和解决方案!立即注册Azure 免费帐户,为挑战做好准备!

文章来源:https://dev.to/azure/how-you-can-learn-to-build-real-time-web-apps-using-net-core-c-and-azure-signalr-service-and-some-javascript-27b0