Azure App Configuration에서 .net 핵심 구성을 동적으로 업데이트


9

내가하려고하는 일 : Azure App Configuration에서 센티넬 키를 사용하여 .net 코어 2.1 mvc 웹 응용 프로그램으로 Azure App Configuration을 설정하려고합니다 .Azure에서 키를 변경할 수 있고 키는 없습니다. 센티넬 값이 변경 될 때까지 내 앱에서 업데이트됩니다. 이론적으로 이것은 안전하게 핫 스왑 구성을 허용해야합니다.

내 문제는 :이 작업을 수행 할 때 IWebHostBuilder에서 센티넬을 감시하는 데 사용할 수있는 WatchAndReloadAll () 메소드가 없으며 대체 Refresh () 메소드가 상태를 구성하는 것처럼 구성을 새로 고치지 않는 것 같습니다.

배경 정보 및 내가 시도한 것 : 지난 주 VS Live-San Diego에 참석하여 Azure App Configuration에 대한 데모를 보았습니다. 구성 값을 암시 할 때 응용 프로그램에서 구성 값을 새로 고치려고하는 데 문제가 있었 으므로이 방법을 설명하는 데모 를 참조했습니다 . 관련 섹션은 약 10 분 정도 소요됩니다. 그러나 해당 방법은 IWebHostBuilder에서 사용할 수없는 것으로 보입니다.

내가 참조 하고있는 문서 : 공식 문서에는이 방법에 대한 참조가 없습니다 .doc quickstart .net coredoc dynamic configuration .net core 참조

내 환경 : Microsoft.Azure.AppConfiguration.AspNetCore 2.0.0-preview-010060003-1250의 최신 미리보기 너겟 패키지와 함께 Visual Studio Enterprise 2019에서 실행되는 닷넷 코어 2.1 사용

내 코드 : 데모에서는 다음과 같이 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>();
}

또한 현재 문서를 사용 하여이 방법으로 시도했습니다.

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>();

그런 다음 시작 클래스에서

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?}");
    });
}

마지막으로 내 설정 구성 모델 :

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

이제 컨트롤러에서 해당 설정을 가져 와서 뷰 백에 넣어 뷰에 표시합니다.

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();
    }
}

변경 사항을 표시하는 간단한보기 :

<!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>

처음으로 구성을 풀 수 있지만 새로 고침 기능은 어떤 식 으로든 작동하지 않는 것 같습니다.

마지막 예에서, 센티넬이 새로운 값으로 설정되었을 때, 또는 최소한 30 초 후에 값이 변경 될 때 설정이 업데이트 될 것으로 예상했습니다. 대기 시간이 없어도 값이 업데이트되며, 앱을 완전히 종료했다가 다시 시작하면 새 구성이로드됩니다.

업데이트 : app.UseAzureAppConfiguration () 추가; 시작시 configure 메소드에서, 구성에 대해 캐시에서 명시 적 시간 종료를 설정하면 고정 된 시간 후에 refresh 메소드가 refresh되도록 수정했지만, 센티넬 기능은 여전히 ​​작동하지 않으며 refresh 메소드에서 updateAll 플래그도 작동하지 않습니다.


구성에 액세스하는 방법 및 위치를 보여줄 수 있습니까? 나는 중 하나의 상황을 모방 한 내 자신의 사업 과는 완벽하게 작동
피터 BONS

다음 ConfigureServices과 같이 startuop.cs 의 메소드 어딘가에 일부 구성 바인딩이 필요합니다.services.Configure<LogSettings>(configuration.GetSection("LogSettings"));
Peter Bons

@peterBons 귀하의 링크는 (404)에 저를 소요
닉 Gasia Robitsch

@PeterBons 구성 주입 / 바인딩에 관한 요청 된 정보를 포함하도록 게시물을 업데이트했습니다. 나는 그것이 효과가 있었기 때문에 당시에는 관련이 없다고 생각했습니다.
Nick Gasia Robitsch

1
그거였다. 천만에요.
피터 본즈

답변:


6

많은 테스트와 시행 착오 끝에 작동합니다.

내 문제는 구성 방법에 대한 Azure 서비스가 누락되었습니다. 여기에 흥미로운 설정이 있습니다. 설정이 여전히 풀다운되고, 이것이 없으면 업데이트되지 않습니다. 일단 이것이 입력되고 문서마다 적절한 센티넬이 구성되면 updateAll 플래그와 함께 작동합니다. 그러나 이것은 현재 문서화되어 있지 않습니다.

해결책은 다음과 같습니다.

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>();
    }
}

그런 다음 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?}");
            });
        }
    }
}

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; }
    }
}

구성이 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 });
        }
    }
}

우리의 견해 :

<!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>

이것이 다른 누군가를 돕기를 바랍니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.