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

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

  • 轮询模型:此方法是默认行为。 系统使用轮询来检测配置更改。 设置的刷新间隔过后,下一次调用 TryRefreshAsyncRefreshAsync 向服务器发送请求。 请求检查配置中的更改。 如果需要,它还会拉取更新的配置。

  • 推送模型:此方法使用 应用配置事件 来检测配置中的更改。 使用此方法,可将应用配置配置为将键值更改事件发送到 Azure 事件网格。 应用程序使用这些事件来优化保持配置更新所需的请求总数。 应用程序可以直接从事件网格或通过 受支持的事件处理程序订阅这些事件。 事件处理程序示例包括 Webhook、Azure Functions 或 Azure 服务总线主题。

在本教程中,你将:

  • 设置一个订阅,用于将配置更改事件从应用配置发送到服务总线主题。
  • 配置您的 .NET 应用程序,以便根据应用配置中的更改来更新其配置。
  • 在应用程序中使用最新配置。

先决条件

  • 完成教程中的步骤时更新的 .NET 应用 :在 .NET 应用中使用动态配置。 本教程介绍如何使用推送刷新在代码中实现动态配置更新。 它基于在 .NET 应用中使用动态配置的教程为基础。
  • Microsoft.Extensions.Configuration.AzureAppConfiguration NuGet 包版本 5.0.0 或更高版本。
  • Azure.Messaging.ServiceBus NuGet 包。
  • 适用于 Windows、macOS 和 Linux 平台的代码编辑器,如 Visual Studio Code

设置服务总线主题和订阅

本教程使用事件网格的服务总线集成来简化配置更改的检测。 如果不希望应用程序持续轮询应用配置更改,可以使用此集成。 服务总线 SDK 提供了一个 API,可用于注册消息处理程序。 在应用配置中检测到更改时,可以使用该处理程序更新配置。

  1. 使用 Azure 门户创建服务总线主题和主题订阅快速入门中的步骤创建服务总线命名空间、主题和订阅。

  2. 使用以下命令设置环境变量。 应用程序代码使用这些变量来注册事件处理程序进行配置更改。

    setx ServiceBusConnectionString "<Service-Bus-namespace-connection-string>"
    setx ServiceBusTopic "<Service-Bus-topic-name>"
    setx ServiceBusSubscription "<Service-Bus-subscription-name>"
    

    运行这些命令后,关闭并重新打开命令提示符,使更改生效。

设置事件订阅

  1. 登录到 Azure 门户,然后从 先决条件中列出的教程转到应用配置存储。

  2. 选择 “事件”,然后选择“ 事件订阅”。

    这是 Azure 门户中应用配置存储概述页的屏幕截图,突出显示了事件和事件订阅。

  3. “创建事件订阅 ”对话框中,输入以下信息:

    • “事件订阅详细信息”下,输入事件订阅的名称。
    • “主题详细信息”下,输入系统主题的名称。
    • “事件类型”下,选择 “修改的键值 ”和 “键值”已删除

    “创建事件订阅”对话框的屏幕截图。突出显示事件类型筛选器列表和订阅和主题名称。

  4. “终结点详细信息”下,进行以下选择:

    • 对于 终结点类型,请选择 “服务总线主题”。
    • “终结点”旁边,选择“ 配置终结点”。

    “创建事件订阅”对话框的屏幕截图。“服务总线主题”终结点类型和“配置终结点”链接突出显示。

  5. “选择服务总线主题 ”对话框中,选择在上一部分设置的服务总线命名空间的订阅和资源组。 此外,选择你设置的命名空间和主题,然后选择“ 确认选择”。

    “选择服务总线主题”对话框的屏幕截图。突出显示所有输入字段和“确认选择”按钮。

  6. 若要创建事件订阅,请选择“ 创建”。

  7. “事件 ”页上,转到“ 事件订阅 ”选项卡,并验证订阅是否存在。

    应用配置存储的“事件”页的屏幕截图。突出显示了列表中的事件、事件订阅和订阅。

注释

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

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

转到包含在 先决条件中列出的教程中使用的 .NET 应用项目的文件夹。 打开 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";
        // An App Configuration connection string uses the following format:
        // Endpoint=https://{store-name}.azconfig.io;Id={id};Secret={secret}

        private const string ServiceBusConnectionStringEnvVarName = "ServiceBusConnectionString";
        // A Service Bus connection string uses the following format:
        // 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);
                    // Load the key-value that has the key "TestApp:Settings:Message" and no label.
                    options.Select("TestApp:Settings:Message");
                    // Reload the configuration when any selected key-values change.
                    options.ConfigureRefresh(refresh =>
                        refresh
                            .RegisterAll()
                            // Important: Reduce the polling frequency.
                            .SetRefreshInterval(TimeSpan.FromDays(1))  
                    );

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

            await 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 async Task 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 an EventGridEvent instance from the notification message.
                EventGridEvent eventGridEvent = EventGridEvent.Parse(BinaryData.FromBytes(processMessageEventArgs.Message.Body));

                // Create a PushNotification instance from the Event Grid event.
                eventGridEvent.TryCreatePushNotification(out PushNotification pushNotification);

                // Prompt a configuration refresh based on the push notification.
                _refresher.ProcessPushNotification(pushNotification);

                return Task.CompletedTask;
            };

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

            await serviceBusProcessor.StartProcessingAsync();
        }
    }
}

在此代码中,调用 ConfigureRefresh 指定应监视所有所选键值以进行更改。 在这种情况下,为监视选择的唯一键是 TestApp:Settings:Message

调用中的 SetRefreshInterval 参数指定在自上次检查以来一天过去一天之前不发出对应用配置的请求。 但是,该方法 ProcessPushNotification 将刷新间隔重置为短暂的随机延迟。 此重置会导致将来调用 RefreshAsyncTryRefreshAsync 重新验证针对应用配置缓存的值,并在需要时更新它们。 因此,当调用 ProcessPushNotification 时,应用程序会在几秒钟内将请求发送到应用配置。 最终结果是应用程序在应用配置存储中发生更改后不久加载新的配置值。 无需不断轮询更新。

如果应用程序错过了更改通知,它仍会在一天内收到有关更新的通知,因为它每天检查配置更改。

如果应用程序或微服务的许多实例使用推送模型连接到同一应用配置存储区,则用于刷新间隔的简短随机延迟非常有用。 如果没有此延迟,应用程序的所有实例可能会在收到更改通知后立即同时将请求发送到应用配置存储。 此行为可能导致应用配置限制应用商店。 默认情况下,刷新间隔延迟设置为介于 0 到 30 秒之间的随机数。 可以使用方法的maxDelay可选参数ProcessPushNotification更改最大值。

该方法 ProcessPushNotification 采用一个 PushNotification 对象,该对象包含有关触发推送通知的应用配置更改的信息。 使用此信息有助于确保在后续配置刷新中加载触发事件的所有配置更改。 该方法 SetDirty 可用于将键值缓存的值标记为脏。 SetDirty但该方法不保证在立即配置刷新中加载触发推送通知的更改。 建议在推送模型中使用 ProcessPushNotification 方法,而不是使用 SetDirty 方法。

在本地生成并运行应用

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

    setx AppConfigurationConnectionString "<App-Configuration-store-connection-string>"
    

    运行此命令后,关闭并重新打开命令提示符,使更改生效。

  2. 运行以下命令以生成控制台应用:

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

    dotnet run
    

    命令提示符窗口的屏幕截图。控制台应用的输出包括一行,其中包含文本初始值:Azure 应用配置中的数据。

  4. 登录到 Azure 门户,然后从 先决条件中列出的教程转到应用配置存储。

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

    密钥 新值
    TestApp:Settings:Message Azure 应用配置中的数据 - 已更新
  6. 等待片刻以便事件被处理。 然后,更新的配置将显示在应用输出中。

    命令提示符窗口的屏幕截图。控制台应用的输出包括一行,其中包含文本“新建”值:Azure 应用配置中的数据 - 已更新。

清理资源

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

重要

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

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

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

后续步骤

在本教程中,使 .NET 应用能够从应用配置动态刷新配置设置。 若要了解如何使用 Azure 托管标识简化对应用配置的访问,请继续学习下一教程。