教程:在 .NET 应用中通过推送刷新使用动态配置

应用配置 .NET 客户端库支持按需更新配置,不需让应用程序重启。 应用程序可以配置为使用以下两种方法中的一种或两种来检测应用配置更改。

  1. 轮询模型:这是使用轮询检测配置更改的默认行为。 设置的缓存值到期后,对 TryRefreshAsyncRefreshAsync 的下一次调用将向服务器发送请求,以检查配置是否已更改,并根据需要拉取已更新的配置。

  2. 推送模型:这将使用应用配置事件检测配置更改。 应用配置设置为向 Azure 事件网格发送键值更改事件后,应用程序就可以使用这些事件来优化保持配置更新所需的请求总数。 应用程序可以选择直接从事件网格订阅这些,也可以选择通过某一受支持的事件处理程序(如 Webhook、Azure 函数或服务总线主题)订阅这些。

本教程演示如何在代码中使用推送刷新实现动态配置更新。 它通过本教程中引入的应用来实现。 接下来,请先按此教程操作:在 .NET应用中使用动态配置

你可以使用任何代码编辑器执行本教程中的步骤。 Visual Studio Code 是 Windows、macOS 和 Linux 平台上提供的一个卓越选项。

在本教程中,你将了解如何执行以下操作:

  • 设置订阅以将配置更改事件从“应用配置”发送到“服务总线”主题
  • 设置 .NET 应用,使其能够更新其配置以响应应用配置更改。
  • 在应用程序中使用最新配置。

先决条件

设置 Azure 服务总线主题和订阅

本教程使用事件网格的服务总线集成来简化不希望持续轮询应用程序配置更改的应用程序的配置更改检测。 Azure 服务总线 SDK 提供了一个用于注册消息处理程序的 API,该消息处理程序可用于在应用配置中检测到更改时更新配置。 遵循快速入门:使用 Azure 门户创建服务总线主题和订阅中的步骤,创建服务总线命名空间、主题和订阅。

创建资源后,添加以下环境变量。 这些环境变量将用于注册事件处理程序,以便获取应用程序代码中的配置更改。

密钥
ServiceBusConnectionString 服务总线命名空间的连接字符串
ServiceBusTopic 服务总线主题的名称
ServiceBusSubscription 服务总线订阅的名称

设置事件订阅

  1. 在 Azure 门户中打开“应用配置”资源,然后在 Events 窗格中单击 + Event Subscription

    应用配置事件

  2. 输入 Event SubscriptionSystem Topic 的名称。

    创建事件订阅

  3. 选择 Endpoint Type 作为 Service Bus Topic,选择“服务总线”主题,然后单击 Confirm Selection

    事件订阅服务总线终结点

  4. 单击 Create 以添加事件订阅。

  5. 单击 Events 窗格中的 Event Subscriptions 以验证订阅是否已成功创建。

    应用配置事件订阅

注意

订阅配置更改时,可以使用一个或多个筛选器来减少发送到应用程序的事件数。 这些筛选器可以配置为事件网格订阅筛选器服务总线订阅筛选器。 例如,订阅筛选器可用于仅订阅以特定字符串开头的键的更改的事件。

注册事件处理程序以从应用配置重新加载数据

打开 Program.cs,使用以下代码更新该文件。

using Azure.Messaging.EventGrid;
using Azure.Messaging.ServiceBus;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
using System;
using System.Threading.Tasks;

namespace TestConsole
{
    class Program
    {
        private const string AppConfigurationConnectionStringEnvVarName = "AppConfigurationConnectionString";
        // e.g. Endpoint=https://{store_name}.azconfig.io;Id={id};Secret={secret}

        private const string ServiceBusConnectionStringEnvVarName = "ServiceBusConnectionString";
        // e.g. Endpoint=sb://{service_bus_name}.servicebus.chinacloudapi.cn/;SharedAccessKeyName={key_name};SharedAccessKey={key}

        private const string ServiceBusTopicEnvVarName = "ServiceBusTopic";
        private const string ServiceBusSubscriptionEnvVarName = "ServiceBusSubscription";

        private static IConfigurationRefresher _refresher = null;

        static async Task Main(string[] args)
        {
            string appConfigurationConnectionString = Environment.GetEnvironmentVariable(AppConfigurationConnectionStringEnvVarName);

            IConfiguration configuration = new ConfigurationBuilder()
                .AddAzureAppConfiguration(options =>
                {
                    options.Connect(appConfigurationConnectionString);
                    options.ConfigureRefresh(refresh =>
                        refresh
                            .Register("TestApp:Settings:Message")
                            .SetCacheExpiration(TimeSpan.FromDays(1))  // Important: Reduce poll frequency
                    );

                    _refresher = options.GetRefresher();
                }).Build();

            RegisterRefreshEventHandler();
            var message = configuration["TestApp:Settings:Message"];
            Console.WriteLine($"Initial value: {configuration["TestApp:Settings:Message"]}");

            while (true)
            {
                await _refresher.TryRefreshAsync();

                if (configuration["TestApp:Settings:Message"] != message)
                {
                    Console.WriteLine($"New value: {configuration["TestApp:Settings:Message"]}");
                    message = configuration["TestApp:Settings:Message"];
                }

                await Task.Delay(TimeSpan.FromSeconds(1));
            }
        }

        private static void RegisterRefreshEventHandler()
        {
            string serviceBusConnectionString = Environment.GetEnvironmentVariable(ServiceBusConnectionStringEnvVarName);
            string serviceBusTopic = Environment.GetEnvironmentVariable(ServiceBusTopicEnvVarName);
            string serviceBusSubscription = Environment.GetEnvironmentVariable(ServiceBusSubscriptionEnvVarName);
            ServiceBusClient serviceBusClient = new ServiceBusClient(serviceBusConnectionString);
            ServiceBusProcessor serviceBusProcessor = serviceBusClient.CreateProcessor(serviceBusTopic, serviceBusSubscription);

            serviceBusProcessor.ProcessMessageAsync += (processMessageEventArgs) =>
                {
                    // Build EventGridEvent from notification message
                    EventGridEvent eventGridEvent = EventGridEvent.Parse(BinaryData.FromBytes(processMessageEventArgs.Message.Body));

                    // Create PushNotification from eventGridEvent
                    eventGridEvent.TryCreatePushNotification(out PushNotification pushNotification);

                    // Prompt Configuration Refresh based on the PushNotification
                    _refresher.ProcessPushNotification(pushNotification);

                    return Task.CompletedTask;
                };

            serviceBusProcessor.ProcessErrorAsync += (exceptionargs) =>
                {
                    Console.WriteLine($"{exceptionargs.Exception}");
                    return Task.CompletedTask;
                };
        }
    }
}

ProcessPushNotification 方法将缓存过期重置为短暂随机延迟。 这导致以后需调用 RefreshAsyncTryRefreshAsync 来根据应用程序配置重新验证已缓存值,并在必要时进行更新。 在本例中,你需要进行注册,以监视缓存过期一天后密钥 TestApp:Settings:Message 的更改情况。 这意味着,在上次检查后的一天内,将不会对应用程序配置发出任何请求。 调用 ProcessPushNotification 方法后,应用程序将在接下来的几秒钟内向应用程序配置发送请求。 并在 App Configuration 存储区中发生更改后立即加载新配置值,而无需不断持续轮询更新。 如果应用程序因某种原因遗漏了更改通知,则它仍将每日检查一次配置更改。

如果应用程序或微服务有多个实例使用推送模型连接到同一个应用程序配置存储区,则缓存过期的短暂随机延迟非常有用。 如果没有此延迟,应用程序的所有实例均可在收到更改通知时同时将请求发送到应用程序配置存储区。 这可能会导致应用程序配置服务对存储区形成限制。 默认缓存过期延迟设置为 0 至 30 秒之间的随机数字,但你可以通过修改 ProcessPushNotification 方法的可选参数 maxDelay 来修改最大延迟。

ProcessPushNotification 方法内存在 PushNotification 对象,该对象包含了应用程序配置中哪项更改触发了推送通知的相关信息。 这有助于确保在以下配置刷新中加载触发事件发生之前的所有配置更改。 SetDirty 方法无法保证触发推送通知的更改会加载到即时配置刷新中。 如果你目前在推送模型中使用的是 SetDirty 方法,建议改用 ProcessPushNotification 方法。

在本地生成并运行应用

  1. 设置名为“AppConfigurationConnectionString”的环境变量,并将其设置为应用配置存储的访问密钥。

    若要使用 Windows 命令提示符在本地生成并运行应用,请运行以下命令,重启命令提示符,使更改生效:

    setx AppConfigurationConnectionString "connection-string-of-your-app-configuration-store"
    
  2. 运行以下命令以生成控制台应用:

    dotnet build
    
  3. 生成成功完成后,请运行以下命令以在本地运行应用:

    dotnet run
    

    在执行更新之前的推送刷新运行

  4. 登录 Azure 门户。 选择“所有资源”,然后选择在快速入门中创建的应用程序配置存储区实例 。

  5. 选择“配置资源管理器” 并更新以下键的值:

    密钥
    TestApp:Settings:Message Azure 应用配置的数据 - 已更新
  6. 等待 30 秒钟,以使事件可被处理且配置可被更新。

    在执行更新之后的推送刷新运行

清理资源

如果不想继续使用本文中创建的资源,请删除此处创建的资源组以避免产生费用。

重要

删除资源组的操作不可逆。 将永久删除资源组以及其中的所有资源。 请确保不要意外删除错误的资源组或资源。 如果在包含要保留的其他资源的资源组中创建了本文的资源,请从相应的窗格中单独删除每个资源,而不是删除该资源组。

  1. 登录到 Azure 门户,然后选择“资源组”。
  2. 在“按名称筛选”框中,输入资源组的名称
  3. 在结果列表中,选择资源组名称以查看概述。
  4. 选择“删除资源组”。
  5. 系统会要求确认是否删除资源组。 重新键入资源组的名称进行确认,然后选择“删除” 。

片刻之后,将会删除该资源组及其所有资源。

后续步骤

在本教程中,你已启用 .NET 应用,可通过应用程序配置动态刷新配置设置。 若要了解如何使用 Azure 托管标识来简化对应用程序配置的访问,请继续学习下一篇教程。