简体   繁体   English

从 Azure 应用配置动态更新 .net 核心配置

[英]Dynamically update .net core config from Azure App Configuration

What I am trying to do: I am attempting to setup Azure App Configuration with a .net core 2.1 mvc web application with a sentinel key in Azure App Configuration, with the goal of being able to change keys in azure, and none of the keys will update in my apps until the sentinel value has changed. What I am trying to do: I am attempting to setup Azure App Configuration with a .net core 2.1 mvc web application with a sentinel key in Azure App Configuration, with the goal of being able to change keys in azure, and none of the keys will在我的应用程序中更新,直到哨兵值发生变化。 In theory, this should allow me to safely hot swap configs.理论上,这应该允许我安全地热交换配置。

What my issue is: When I do this there is no WatchAndReloadAll() method available to watch the sentinel on the IWebHostBuilder, and the alternative Refresh() methods do not seem to refresh the configuration as they state.我的问题是:当我这样做时,没有 WatchAndReloadAll() 方法可用于观察 IWebHostBuilder 上的哨兵,并且替代的 Refresh() 方法似乎不会刷新配置,因为它们是 state。

Background Information, and what I have tried: I attended VS Live - San Diego, this past week and watched a demo on Azure App Configuration.背景信息,以及我尝试过的内容:上周我参加了 VS Live - San Diego,并观看了有关 Azure 应用程序配置的演示。 I had some problems trying to get the application to refresh config values when implimenting it, so I also referenced this demo describing how to do this as well.我在尝试让应用程序刷新配置值时遇到了一些问题,因此我还参考了这个演示,描述了如何执行此操作。 The relevant section is at about 10 minutes in. However, that method does not appear to be available on the IWebHostBuilder.相关部分大约在 10 分钟后。但是,该方法在 IWebHostBuilder 上似乎不可用。

Documentation I am referencing: In the official documentation there is no reference to this method see doc quickstart .net core and doc dynamic configuration .net core我正在参考的文档:在官方文档中没有提及此方法,请参阅文档快速入门 .net 内核文档动态配置 .net 内核

My Environment: Using dot net core 2.1 being run from Visual Studio Enterprise 2019, with the latest preview nuget package for Microsoft.Azure.AppConfiguration.AspNetCore 2.0.0-preview-010060003-1250 My Environment: Using dot net core 2.1 being run from Visual Studio Enterprise 2019, with the latest preview nuget package for Microsoft.Azure.AppConfiguration.AspNetCore 2.0.0-preview-010060003-1250

My Code: In the demo, they created a IWebHostBuilder via the CreateWebHostBuilder(string[] args) method like so:我的代码:在演示中,他们通过 CreateWebHostBuilder(string[] args) 方法创建了一个 IWebHostBuilder,如下所示:

public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
    return WebHost.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        var settings = config.Build();
        config.AddAzureAppConfiguration(options =>
        {
            options.Connect(settings["ConnectionStrings:AzureConfiguration"])
            .Use(keyFilter: "TestApp:*")
            .WatchAndReloadAll(key: "TestApp:Sentinel", pollInterval: TimeSpan.FromSeconds(5));
        }); 
    })
    .UseStartup<Startup>();
}

I also tried it this way, using the current documentation:我也尝试过这种方式,使用当前的文档:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        var settings = config.Build();

        config.AddAzureAppConfiguration(options =>
        {
            // fetch connection string from local config. Could use KeyVault, or Secrets as well.
            options.Connect(settings["ConnectionStrings:AzureConfiguration"])
            // filter configs so we are only searching against configs that meet this pattern
            .Use(keyFilter: "WebApp:*")
            .ConfigureRefresh(refreshOptions =>
            { 
                // In theory, when this value changes, on the next refresh operation, the config will update all modified configs since it was last refreshed.
                refreshOptions.Register("WebApp:Sentinel", true);
                refreshOptions.Register("WebApp:Settings:BackgroundColor", false);
                refreshOptions.Register("WebApp:Settings:FontColor", false);
                refreshOptions.Register("WebApp:Settings:FontSize", false);
                refreshOptions.Register("WebApp:Settings:Message", false);
            });
        });
    })
    .UseStartup<Startup>();

Then, in my startup class:然后,在我的启动 class 中:

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<Settings>(Configuration.GetSection("WebApp:Settings"));
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseAzureAppConfiguration();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

and finally my settings config model:最后是我的设置配置 model:

public class Settings
{
    public string BackgroundColor { get; set; }
    public long FontSize { get; set; }
    public string FontColor { get; set; }
    public string Message { get; set; }
}

Now, in my controller, I pull those settings and throw them into a view bag to be displayed on the view.现在,在我的 controller 中,我拉出这些设置并将它们放入视图包中以显示在视图上。

public class HomeController : Controller
{
    private readonly Settings _Settings;

    public HomeController(IOptionsSnapshot<Settings> settings)
    {
        _Settings = settings.Value;
    }

    public IActionResult Index()
    {
        ViewData["BackgroundColor"] = _Settings.BackgroundColor;
        ViewData["FontSize"] = _Settings.FontSize;
        ViewData["FontColor"] = _Settings.FontColor;
        ViewData["Message"] = _Settings.Message;

        return View();
    }
}

A simple view to display the changes:显示更改的简单视图:

<!DOCTYPE html>
<html lang="en">
<style>
    body {
        background-color: @ViewData["BackgroundColor"]
    }
    h1 {
        color: @ViewData["FontColor"];
        font-size: @ViewData["FontSize"];
    }
</style>
<head>
    <title>Index View</title>
</head>
<body>
    <h1>@ViewData["Message"]</h1>
</body>
</html>

I can get it to pull the config down the first time, however, the refresh functionality does not appear to work in any way.我可以让它第一次拉下配置,但是,刷新功能似乎无法以任何方式工作。

In the last example, I expected the configs to update when the sentinel was set to any new value, or at the very least, to update a value 30 seconds after it was changed.在最后一个示例中,我希望配置在哨兵设置为任何新值时更新,或者至少在更改值 30 秒后更新值。 No length of waiting updates the values, and only a full shut down and restart of the app loads the new config.没有等待时间更新值,只有完全关闭并重新启动应用程序才会加载新配置。

Update: Adding app.UseAzureAppConfiguration();更新:添加 app.UseAzureAppConfiguration(); in the configure method on startup, and setting an explicit timeout on the cache for the config fixed the refresh method to refresh after a fixed amount of time, but the sentinel functionality still does not work, nor does the updateAll flag on the refresh method.在启动时的配置方法中,并在配置缓存上设置显式超时修复了刷新方法在固定时间后刷新,但哨兵功能仍然不起作用,刷新方法上的 updateAll 标志也不起作用。

Ok, after much testing and trial and error, I have it working.好的,经过多次测试和反复试验,我让它工作了。

My issue was a missing service for azure on the configure method.我的问题是在配置方法上缺少 azure 服务。 There is some interesting behaviour here, in that it will still pull down the settings, it just wont update, if this is missing.这里有一些有趣的行为,因为它仍然会下拉设置,如果缺少它就不会更新。 So once this was put in, and with a proper sentinel configured per documentation, it works with the updateAll flag.因此,一旦将其放入并为每个文档配置了适当的哨兵,它就可以与 updateAll 标志一起使用。 However this isn't currently documented.然而,这目前没有记录。

Here is the solution:这是解决方案:

In Program.cs:在 Program.cs 中:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;

namespace ASPNetCoreApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }   // Main

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                var settings = config.Build();

                config.AddAzureAppConfiguration(options =>
                {
                    // fetch connection string from local config. Could use KeyVault, or Secrets as well.
                    options.Connect(settings["ConnectionStrings:AzureConfiguration"])
                    // filter configs so we are only searching against configs that meet this pattern
                    .Use(keyFilter: "WebApp:*")
                    .ConfigureRefresh(refreshOptions =>
                    { 
                        // When this value changes, on the next refresh operation, the config will update all modified configs since it was last refreshed.
                        refreshOptions.Register("WebApp:Sentinel", true);
                        // Set a timeout for the cache so that it will poll the azure config every X timespan.
                        refreshOptions.SetCacheExpiration(cacheExpirationTime: new System.TimeSpan(0, 0, 0, 15, 0));
                    });
                });
            })
            .UseStartup<Startup>();
    }
}

Then in Startup.cs:然后在 Startup.cs 中:

using ASPNetCoreApp.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace ASPNetCoreApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // bind the config to our DI container for the settings we are pulling down from azure.
            services.Configure<Settings>(Configuration.GetSection("WebApp:Settings"));
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            // Set the Azure middleware to handle configuration
            // It will pull the config down without this, but will not refresh.
            app.UseAzureAppConfiguration();
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

The Settings model I am binding my azure retrieved data to:设置 model 我将 azure 检索到的数据绑定到:

namespace ASPNetCoreApp.Models
{
    public class Settings
    {
        public string BackgroundColor { get; set; }
        public long FontSize { get; set; }
        public string FontColor { get; set; }
        public string Message { get; set; }
    }
}

A generic home controller with the config being set to the ViewBag to pass in to our view:一个通用的 home controller 配置被设置为 ViewBag 以传递给我们的视图:

using ASPNetCoreApp.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Diagnostics;

namespace ASPNetCoreApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly Settings _Settings;

        public HomeController(IOptionsSnapshot<Settings> settings)
        {
            _Settings = settings.Value;
        }
        public IActionResult Index()
        {
            ViewData["BackgroundColor"] = _Settings.BackgroundColor;
            ViewData["FontSize"] = _Settings.FontSize;
            ViewData["FontColor"] = _Settings.FontColor;
            ViewData["Message"] = _Settings.Message;

            return View();
        }

        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Your contact page.";

            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Our View:我们的观点:

<!DOCTYPE html>
<html lang="en">
<style>
    body {
        background-color: @ViewData["BackgroundColor"]
    }
    h1 {
        color: @ViewData["FontColor"];
        font-size: @ViewData["FontSize"];
    }
</style>
<head>
    <title>Index View</title>
</head>
<body>
    <h1>@ViewData["Message"]</h1>
</body>
</html>

Hope this helps someone else!希望这对其他人有帮助!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 .NET Core 2.2 - 无法从 Azure AppConfig 获取应用程序配置 - .NET Core 2.2 - Unable to get app configuration from Azure AppConfig Azure 应用服务 - 更新 .NET Core 运行时 - Azure App Service - update .NET Core runtime 如何从 Azure 应用服务连接字符串设置 ASP.Net Core exe.config 值 - How can I set an ASP.Net Core exe.config value from Azure App Service Connection String 如何让 IOptionsMonitor<T> 从托管在 Azure Windows Server VM 上的正在运行的 .NET Core 2.2 应用程序获取最新配置值? - How to let IOptionsMonitor<T> get the latest configuration value from a running .NET Core 2.2 app hosted on an Azure Windows Server VM? 在 Azure Function v2 (.NET Core) 应用程序中读取 .NET Fx app.config - Reading .NET Fx app.config in an Azure Function v2 (.NET Core) app 动态切换配置文件(Web或app config) - Dynamically switch the configuration file (web or app config) 覆盖ASP.Net 5中Azure Web App中config.json文件中的配置值 - Overriding Configuration Values in config.json file in Azure Web App in ASP.Net 5 如何从 DBContext(实体框架核心)中的 App.config(不是 .net 核心应用程序)中读取值 - How to read value from App.config (not .net core app) in DBContext (which is entity framework core) ASP.Net Core - 配置管理器不从 Web.config 加载数据 - ASP.Net Core - Configuration Manager doesn't load data from Web.config 如何从* .config文件以外的其他位置进行动态生成的.net服务客户端读取配置 - How to make dynamically generated .net service client read configuration from another location than *.config files
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM