Perbarui konfigurasi .net core secara dinamis dari Konfigurasi Aplikasi Azure

9

Apa yang saya coba lakukan: Saya mencoba mengatur Konfigurasi Aplikasi Azure dengan aplikasi web .net inti 2.1 mvc dengan kunci sentinel di Konfigurasi Aplikasi Azure, dengan tujuan untuk dapat mengubah kunci dalam warna biru, dan tidak ada tombol akan memperbarui di aplikasi saya sampai nilai sentinel telah berubah. Secara teori, ini seharusnya memungkinkan saya untuk aman menukar konfigurasi.

Apa masalah saya adalah: Ketika saya melakukan ini, tidak ada metode WatchAndReloadAll () yang tersedia untuk menonton sentinel di IWebHostBuilder, dan metode Refresh () alternatif tampaknya tidak menyegarkan konfigurasi ketika mereka menyatakan.

Informasi Latar Belakang, dan apa yang telah saya coba: Saya menghadiri VS Live - San Diego, minggu terakhir ini dan menonton demo di Azure App Configuration. Saya punya beberapa masalah dalam mencoba mendapatkan aplikasi untuk menyegarkan nilai konfigurasi ketika melakukan implimenting, jadi saya juga mereferensikan demo ini yang menjelaskan cara melakukan ini juga. Bagian yang relevan sekitar 10 menit. Namun, metode itu tampaknya tidak tersedia di IWebHostBuilder.

Dokumentasi yang saya maksudkan: Dalam dokumentasi resmi tidak ada referensi untuk metode ini lihat doc quickstart .net core dan konfigurasi dinamis doc .net core

Lingkungan Saya: Menggunakan dot net core 2.1 dijalankan dari Visual Studio Enterprise 2019, dengan paket nuget pratinjau terbaru untuk Microsoft.Azure.AppConfiguration.AspNetCore 2.0.0-preview-010060003-1250

Kode Saya: Dalam demo, mereka membuat IWebHostBuilder melalui metode CreateWebHostBuilder (string [] args) seperti:

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

Saya juga mencobanya dengan menggunakan dokumentasi saat ini:

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

Kemudian, di kelas startup saya:

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

dan akhirnya model konfigurasi pengaturan saya:

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

Sekarang, di pengontrol saya, saya menarik pengaturan itu dan melemparkannya ke dalam tas tampilan untuk ditampilkan pada tampilan.

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

Tampilan sederhana untuk menampilkan perubahan:

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

Saya bisa mendapatkannya untuk menarik konfigurasi ke bawah pertama kali, namun, fungsi refresh tampaknya tidak berfungsi dengan cara apa pun.

Dalam contoh terakhir, saya mengharapkan konfigurasi untuk memperbarui ketika sentinel diatur ke nilai baru, atau paling tidak, untuk memperbarui nilai 30 detik setelah itu diubah. Tidak lama menunggu pembaruan nilai-nilai, dan hanya shut down dan restart penuh aplikasi memuat konfigurasi baru.

Pembaruan: Menambahkan app.UseAzureAppConfiguration (); dalam metode configure pada startup, dan pengaturan timeout eksplisit pada cache untuk konfigurasi memperbaiki metode refresh untuk menyegarkan setelah jumlah waktu yang tetap, tetapi fungsionalitas sentinel masih tidak berfungsi, juga pembaruanSemua tanda pada metode refresh.

Nick Gasia Robitsch
sumber
Bisakah Anda menunjukkan kepada saya bagaimana & di mana Anda mengakses konfigurasi? Saya telah meniru situasi Anda di salah satu proyek saya sendiri dan berfungsi dengan baik
Peter Bons
Saya mengharapkan beberapa konfigurasi yang mengikat di suatu tempat dalam ConfigureServicesmetode Anda di startuop.cs, seperti services.Configure<LogSettings>(configuration.GetSection("LogSettings"));
Peter Bons
@ PeterBons, tautan Anda membawa saya ke 404.
Nick Gasia Robitsch
@PeterBons Saya telah memperbarui posting saya untuk memasukkan informasi yang diminta sehubungan dengan konfigurasi injeksi / pengikatan. Saya tidak berpikir itu dirilis pada saat itu karena itu berfungsi.
Nick Gasia Robitsch
1
Itu dia. Sama-sama.
Peter Bons

Jawaban:

6

Ok, setelah banyak pengujian dan coba-coba, saya membuatnya berfungsi.

Masalah saya adalah layanan yang hilang untuk azure pada metode configure. Ada beberapa perilaku yang menarik di sini, karena masih akan menarik pengaturan, itu hanya tidak akan memperbarui, jika ini hilang. Jadi, begitu ini dimasukkan, dan dengan sentinel yang dikonfigurasi dengan tepat per dokumentasi, ia bekerja dengan flag updateAll. Namun ini saat ini tidak didokumentasikan.

Ini solusinya:

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

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

Model Pengaturan saya mengikat data saya yang diambil biru ke:

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

Pengontrol rumah umum dengan konfigurasi yang diatur ke ViewBag untuk diteruskan ke tampilan kami:

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

Pandangan kami:

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

Semoga ini bisa membantu orang lain!

Nick Gasia Robitsch
sumber