快速入门:通过 C# 使用 Azure Functions 和 SignalR 服务创建一个可显示 GitHub 星数的应用
在本文中,你会了解如何使用 C# 通过 SignalR 服务和 Azure Functions 生成一个无服务器应用程序,用于将消息广播到客户端。
先决条件
本快速入门需要满足以下先决条件:
- Visual Studio Code 或其他代码编辑器。 如果尚未安装 Visual Studio Code,请在此处下载 Visual Studio Code。
- Azure 订阅。 如果还没有 Azure 订阅,可以在开始前创建一个免费帐户。
- Azure Functions Core Tools
- .NET Core SDK
创建 Azure SignalR 服务实例
在本部分中,你将创建一个基本 Azure SignalR 实例来用于你的应用。 以下步骤使用 Azure 门户创建新实例,但你也可以使用 Azure CLI。 有关详细信息,请参阅 Azure SignalR 服务 CLI 参考中的 az signalr create 命令。
- 登录 Azure 门户。
- 在页面的左上角,选择“+ 创建资源” 。
- 在“创建资源”页上,在“搜索服务和市场”文本框中,输入“signalr”,然后从列表中选择“SignalR 服务”。
- 在“SignalR 服务”页上,选择“创建”。
- 在“基本信息”选项卡上,输入新 SignalR 服务实例的基本信息。 输入以下值:
字段 | 建议的值 | 描述 |
---|---|---|
订阅 | 选择订阅 | 选择要用于创建新的 SignalR 服务实例的订阅。 |
资源组 | 创建一个名为 SignalRTestResources 的资源组 | 为 SignalR 资源选择或创建资源组。 对于本教程,创建新的资源组比使用现有资源组更为合适。 若要在完成本教程后释放资源,请删除资源组。 删除资源组还会删除属于该组的所有资源。 此操作不能撤消。 删除资源组之前,请确保它不包含你希望保留的资源。 有关详细信息,请参阅 Using resource groups to manage your Azure resources(使用资源组管理 Azure 资源)。 |
资源名称 | testsignalr | 输入用于 SignalR 资源的唯一资源名称。 如果你的区域中已使用了 testsignalr,请添加一个数字或字符,以将名称设为唯一。 该名称必须是包含 1 到 63 个字符的字符串,只能包含数字、字母和连字符 ( - ) 字符。 该名称的开头或末尾不能是连字符字符,并且连续的连字符字符无效。 |
区域 | 选择你的区域 | 为新的 SignalR 服务实例选择合适的区域。 Azure SignalR 服务当前并非在所有区域中都可用。 有关详细信息,请参阅 Azure SignalR 服务区域可用性。 |
定价层 | 选择“更改”,然后选择“免费(仅限开发/测试)”。 选择“选择”以确认你选择的定价层。 | Azure SignalR 服务有三个定价层:免费、标准和高级。 教程使用的是免费层,除非在先决条件中另行说明。 若要详细了解各个层级之间的功能差异以及定价,请参阅 Azure SignalR 服务定价 |
服务模式 | 选择适当的服务模式 | 在 Web 应用中托管 SignalR 中心逻辑并使用 SignalR 服务作为代理时,请使用“默认”。 使用无服务器技术(如 Azure Functions)托管 SignalR 中心逻辑时,请使用“无服务器”。 “经典”模式仅用于向后兼容,不建议使用。 有关详细信息,请参阅 Azure SignalR 服务中的服务模式。 |
对于 SignalR 教程,你不需要更改“网络”和“标记”选项卡上的设置。
- 选择“基本信息”选项卡底部的“查看 + 创建”按钮。
- 在“查看 + 创建”选项卡上检查各个值,然后选择“创建”。 部署需要几分钟时间才能完成。
- 在部署完成后,选择“转到资源组”按钮。
- 在 SignalR 资源页面上,从左侧菜单中选择“设置”下的“密钥”。
- 复制主密钥的连接字符串。 在本教程中,稍后你将需要使用此连接字符串来配置你的应用。
在本地安装和运行 Azure Functions
此步骤需要 Azure Functions Core Tools。
创建一个空目录并使用命令行更改到该目录。
初始化新项目。
使用代码编辑器创建名为 Function.cs 的新文件。 将以下代码添加到 Function.cs:
using System; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Azure.WebJobs.Extensions.SignalRService; using Newtonsoft.Json; namespace CSharp { public static class Function { private static HttpClient httpClient = new HttpClient(); private static string Etag = string.Empty; private static string StarCount = "0"; [FunctionName("index")] public static IActionResult GetHomePage([HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req, ExecutionContext context) { var path = Path.Combine(context.FunctionAppDirectory, "content", "index.html"); return new ContentResult { Content = File.ReadAllText(path), ContentType = "text/html", }; } [FunctionName("negotiate")] public static SignalRConnectionInfo Negotiate( [HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req, [SignalRConnectionInfo(HubName = "serverless")] SignalRConnectionInfo connectionInfo) { return connectionInfo; } [FunctionName("broadcast")] public static async Task Broadcast([TimerTrigger("*/5 * * * * *")] TimerInfo myTimer, [SignalR(HubName = "serverless")] IAsyncCollector<SignalRMessage> signalRMessages) { var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/azure/azure-signalr"); request.Headers.UserAgent.ParseAdd("Serverless"); request.Headers.Add("If-None-Match", Etag); var response = await httpClient.SendAsync(request); if (response.Headers.Contains("Etag")) { Etag = response.Headers.GetValues("Etag").First(); } if (response.StatusCode == System.Net.HttpStatusCode.OK) { var result = JsonConvert.DeserializeObject<GitResult>(await response.Content.ReadAsStringAsync()); StarCount = result.StarCount; } await signalRMessages.AddAsync( new SignalRMessage { Target = "newMessage", Arguments = new[] { $"Current star count of https://github.com/Azure/azure-signalr is: {StarCount}" } }); } private class GitResult { [JsonRequired] [JsonProperty("stargazers_count")] public string StarCount { get; set; } } } }
Function.cs 中的代码有三个函数:
GetHomePage
用作客户端来获取网站。Negotiate
由客户端用于获取访问令牌。Broadcast
定期进行调用以从 GitHub 获取星数,然后向所有客户端广播消息。
本示例的客户端界面是网页。 我们通过从文件 content/index.html 读取 HTML 内容,使用
GetHomePage
函数呈现网页。 现在,让我们使用以下内容在content
子目录下创建此 index.html:<html> <body> <h1>Azure SignalR Serverless Sample</h1> <div id="messages"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.min.js"></script> <script> let messages = document.querySelector('#messages'); const apiBaseUrl = window.location.origin; const connection = new signalR.HubConnectionBuilder() .withUrl(apiBaseUrl + '/api') .configureLogging(signalR.LogLevel.Information) .build(); connection.on('newMessage', (message) => { document.getElementById("messages").innerHTML = message; }); connection.start() .catch(console.error); </script> </body> </html>
更新
*.csproj
以在生成输出文件夹中制作内容页面。<ItemGroup> <None Update="content/index.html"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
Azure Functions 需要一个存储帐户才能工作。 可以安装并运行 Azurite 存储模拟器。 或者,可以使用以下命令更新设置以使用实际存储帐户:
func settings add AzureWebJobsStorage "<storage-connection-string>"
现在即将完成了。 最后一步是将 SignalR 服务的连接字符串设置为 Azure Functions 设置。
通过在门户顶部的搜索框中搜索 SignalR 服务实例的名称,确认该实例已成功创建。 选择该实例以将其打开。
选择“密钥”以查看 SignalR 服务实例的连接字符串。
复制主连接字符串,然后运行以下命令:
func settings add AzureSignalRConnectionString "<signalr-connection-string>"
在本地运行 Azure 函数:
func start
在本地运行 Azure 函数后,打开
http://localhost:7071/api/index
,你可以看到当前星数。 如果你在 GitHub 中添加了星标或取消添加了星标,星数会每隔几秒刷新一次。
清理资源
如果不打算继续使用此应用,请按照以下步骤删除本快速入门中创建的所有资源,以免产生任何费用:
在 Azure 门户的最左侧选择“资源组”,,然后选择创建的资源组。 或者,可以使用搜索框按名称查找资源组。
在打开的窗口中选择资源组,然后单击“删除资源组”。
在新窗口中键入要删除的资源组的名称,然后单击“删除”。
遇到问题? 请试用故障排除指南。
后续步骤
在本快速入门中,你在本地生成并运行了一个实时的无服务器应用程序。 接着,详细了解如何通过 Azure SignalR 服务在客户端与 Azure Functions 之间进行双向通信。