Artikel Hoe consumeer je items uit een Azure App Configuration Service

Inleiding

In mijn blog (Azure App Configuration wat is het en hoe automatiseer je het met Azure DevOps) heb ik uitgelegd wat een Azure App Configuration is, waarom je dit zou willen gebruiken en hoe je dit kunt automatiseren. In deze blog ga ik uitleggen hoe een applicatie zijn configuratie uit een Azure Application Configuration Service kan uitlezen en gebruiken.

Voorkennis

Ik ga er omwille van de lengte van de blog vanuit dat bekend is hoe je een dotnet core Web applicatie aanmaakt. Voor de mensen die dit nog niet eerder hebben gedaan, is op deze pagina Quickstart ASP.NET Core Web app meer informatie te vinden.

Configuratie model

Ervan uitgaande dat het aanmaken van een webapp is gelukt, beginnen we met het maken van het model dat onze configuratie-items gaat bevatten. Mijn model ziet er als volgt uit:

public class MySettings
{
  public string Message { get; set; }

  public string ConfigVersion { get; set; }
}

Een settings voor een melding die we straks op de pagina gaan plaatsen en een configuratie versie die we straks gaan gebruiken om aan de applicatie aan te geven dat de configuratie is gewijzigd en dat deze opnieuw ingelezen dient te worden.

Appsettings JSON bestand

De standaard manier van configuratie in dotnet core is met json bestanden. De appsettings.json zou er op basis van bovenstaande model als volgt uitzien:

    {
       "Logging": {
          "LogLevel": {
             "Default": "Information",
             "Microsoft": "Warning",
             "Microsoft.Hosting.Lifetime": "Information"
          }
        },
        "AllowedHosts": "*",
        "blogSection": {
           "message": "App says hi from config file",
           "configVersion":  "0.1"
        }
    }

Configuratie consumeren

Om de configuratie settings te kunnen gebruiken moeten we in de startup.cs aangeven dat we de ‘blogSection’ willen deserializeren naar een MySettings class instance. Open hiervoor het bestand startup.cs en in de methode ‘ConfigureServices’ voeg de volgende regel toe:

services.Configure<MySettings>(Configuration.GetSection("blogSection"));

De code van ConfigureServices ziet er nu als volgt uit:


      // This method gets called by the runtime. Use this method to add services to the container.
      public void ConfigureServices(IServiceCollection services)
      {
         services.Configure<MySettings>(Configuration.GetSection("blogSection"));
    
         services.AddRazorPages();
      }

Nu we de dependency injection hebben verteld hoe MySettings moet worden aangemaakt wanneer er om wordt gevraagd, kunnen we elders in onze applicatie de MySettings gaan consumeren en Message op het scherm gaan tonen.

Open hiervoor onder de Pages map de bestanden Index.cshtml en Index.cshtml.cs. Hier is Index.cshtml.cs het viewmodel voor de Index view (Index.cshtml). In het model maken we een MySettings private member aan die met behulp van dependency injection van een waarde wordt voorzien.

Vervolgens voegen we een property toe aan het model waarmee de Message uit de config opgevraagd kan worden. Het model ziet er nu als volgt uit

    public class IndexModel : PageModel
    {
      private readonly ILogger<IndexModel> _logger;
      private readonly MySettings _settings;
    
      public IndexModel(ILogger<IndexModel> logger, IOptions<MySettings> settingsOptions)
      {
         _logger = logger;
         _settings = settingsOptions.Value;
      }
    
      public string Message => _settings.Message;
    
      public void OnGet()
      {
    
      }
    }

Nu zijn we klaar en willen we natuurlijk het eindresultaat zien. Start de applicatie (F5). De applicatie wordt nu gebuild en er wordt een browser gestart met daarin de homepage van onze webapp. Als alles goed is gegaan ziet het er nu als volgt uit:

Screenshot startpagina

Op dit moment hebben we de basis op orde. We hebben een applicatie gemaakt die configuratie uitleest en op het scherm toont. Met deze manier van werken met de configuratie zou je nu de message kunnen overriden met een environment specifieke configuratie (bijv. appSettings.Development.json). Om dit goed te laten werken moet er wel een environment variabele ASPNETCORE_ENVIRONMENT gezet worden en dan met de waarde Development. Een makkelijke manier om environment variabelen te zetten is door deze in de launchsettings.json (Properties map) te zetten of bij de WebApp properties (op Debug tab). Als het goed is staat hier standaard deze environment variabele al ingevuld. Mocht dit niet het geval zijn vul deze hier dan in zoals in onderstaande screenshot is te zien.

Zetten van Environment variabele

Je zou hier dus nu ook op de ‘Add’ knop kunnen klikken en een Environment variabele kunnen toevoegen met de naam ‘blogSection__message’ en met de waarde ‘Hi from Environment variable’. Herstart de applicatie en je zult zien dat de melding op het scherm is veranderd.

Screenshot startpagina

Consumeren van items in een Azure App Configuration

Nu willen we de configuratie niet meer vanuit de config files uitlezen maar vanuit een Azure App Configuration.

Ten eerste je mag niet anoniem naar Azure, dus we moeten ervoor zorgen dat we geauthenticeerd naar het Azure App Configuration gaan. Hiervoor moeten we een nuget package toevoegen aan het Web App project. Het toe te voegen nuget package is ‘Azure Identity’. Wanneer deze is toegevoegd krijg je bij het context menu van het webapp project (rechtermuis klik op project) een extra optie met de naam ‘Manage User Secret’. Klik deze aan en er wordt een secrets.json bestand geopend. Deze is nu nog leeg, maar hier gaan we straks de connectionstring naar ons Azure App Configuration toevoegen.

Maar eerst moeten we nog een drietal environment variabelen toevoegen. Dit doe je op dezelfde manier als hierboven beschreven. Het betreft:

  • AZURE_CLIENT_ID
  • AZURE_CLIENT_SECRET
  • AZURE_TENANT_ID

Zetten Azure Environment variabelen

Bovenstaande informatie (Client_ID, Client_Secret en Tenant_ID) komen uit de Azure AD. Meer info hoe dit te configureren vind je hier: (howto-create-service-principal-portal) Zodra deze environment variabelen zijn gezet zal de applicatie informatie meesturen naar Azure zodat Azure ‘weet’ wie of wat de connectie opzet en kan controleren of de rechten daarvoor op orde zijn.

Nu kunnen we de connectstring naar de app configuration service configureren in de eerder genoemde secrets.json. De connectionstring kan je kopieren in de Azure Portal. Hiervoor open je Azure App Configuration resource en kiest in het linker menu voor ‘AccessKeys’. Op deze pagina vind je een Primary Key en een Secondary Key. Kopieer van de Primary Key de Connection String (zie rode vierkant in onderstaande screenshot)

Kopieren Azure App Configuration ConnectionString

Plak het zojuist gekopieerde in de secrets.json zodat het er als onderstaande json uitziet.

{
    "connectionStrings.AppConfiguration": "<connectionstring copied from App Configuration Resource AccessKeys (Azure Portal)>"
}

Het voorbereidende werk is nu klaar, nu moeten we de applicatie aanpassen zodat deze de nieuwe instellingen gaat gebruiken.

Hiervoor gaan we het volgende doen: Er is een tweede nuget package nodig, deze keer betreft het het package met de naam Microsoft.Azure.AppConfiguration.AspNetCore.

Nadat dit package is toegevoegd openen we de program.cs. Bij de CreateHostBuilder methode breiden we de webBuilder configuratie uit. Standaard staat hier alleen maar ‘webBuilder.UseStartup()’ we voegen hier een regel aan toe om aan te geven dat de we een IConfigurationBuilder willen die de configuratie-items uit het Azure App Configuration service wil halen.

De code ziet er dan als volgt uit.

public class Program
{
   public static void Main(string[] args)
   {
      CreateHostBuilder(args).Build().Run();
   } 

   public static IHostBuilder CreateHostBuilder(string[] args) =>
       Host.CreateDefaultBuilder(args)
           .ConfigureWebHostDefaults(webBuilder =>
           {
              webBuilder
                 .ConfigureAppConfiguration(ConfigureAzureAppConfiguration)
                 .UseStartup<Startup>();
           });

   private static void ConfigureAzureAppConfiguration(WebHostBuilderContext hostingContext, IConfigurationBuilder config)
   {
      const string DefaultConfigurationSettingsLabel = "Default";
      string environmentName = hostingContext.HostingEnvironment.EnvironmentName;

      var settings = config.Build();
      config.AddAzureAppConfiguration(o => o.Connect(settings["connectionStrings.AppConfiguration"])
         .ConfigureKeyVault(kv => { kv.SetCredential(new DefaultAzureCredential()); })
         .Select(KeyFilter.Any, labelFilter: DefaultConfigurationSettingsLabel)
         .Select(KeyFilter.Any, environmentName)
         .ConfigureRefresh(refresh => { refresh.Register("BLOG:blogSection:configVersion", label: DefaultConfigurationSettingsLabel, refreshAll: true); }));
   }
}

De code in de ConfigureAzureAppConfiguration methode zal ik hier uitleggen.

De ConfigureKeyVault aanroep is nodig om aan te geven met welke credentials een eventuele Key Vault aangeroepen dient te worden.

De ‘AddAzureAppConfiguration’ aanroep spreekt voor zich. We geven hier aan dat we de config uit Azure willen halen en gebruiken hiervoor de eerder gekopieerde connectionstring uit de secrets.json. Met de Select aanroepen geeft je aan welke keys je uit de App Configuration service wilt uitlezen en daarbij geef je tevens aan welk label deze keys moeten hebben. Ik heb er voor gekozen om de settings die niet omgevingsspecifiek zijn te voorzien van het label ‘Default’. De omgevingsspecifieke settings hebben een label gelijk aan de waarde van de eerdergenoemde ASPNETCORE_ENVIRONMENT variabele.

Met de laatste regel wordt aangegeven wanneer je wilt dat de configuratiesettings opnieuw ingelezen dienen te worden. In dit geval, wanneer de config key ‘BLOG:blogSection:configVersion’ een nieuwe waarde krijgt in de Azure Portal, is dat een signaal voor de applicatie om de configuratie te refreshen.

Om deze applicatie nu te testen voer in de Azure Portal bij de eerder aangemaakte App Configuration service 2 items op. Ga hiervoor naar de AppConfiguration resource en klik op ‘Configuration Explorer’ in het linker menu. Bovenaan zie je de optie ‘Create’. Klik hierop en kies voor ‘Key-Value’.

Key: “BLOG:blogSection:message”
Value: “Hi from Azure App Configuration”

Key: “BLOG:blogSection:configVersion”
Value: “0.1”

In de startup.cs moeten we nu de regel

services.Configure<MySettings>(Configuration.GetSection(“blogSection”)

aanpassen naar:

services.Configure<MySettings>(Configuration.GetSection(“BLOG:blogSection”)

De code van ConfigureServices ziet er dan als volgt uit:

  // This method gets called by the runtime. Use this method to add services to the container.
  public void ConfigureServices(IServiceCollection services)
  {
     services.Configure<MySettings>(Configuration.GetSection("BLOG:blogSection"));

     services.AddRazorPages();
  }

Daarnaast moeten we nog een middleware toevoegen om het refreshen van de configuratie goed te ondersteunen. In de startup in de ‘Configure’ methode moeten we de volgende regel toevoegen:

app.UseAzureAppConfiguration();

De code van Configure ziet er dan als volgt uit:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  if (env.IsDevelopment())
  {
     app.UseDeveloperExceptionPage();
  }
  else
  {
     app.UseExceptionHandler("/Error");
     // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts
     app.UseHsts();
  }

  app.UseHttpsRedirection();
  app.UseStaticFiles();

  app.UseRouting();

  app.UseAzureAppConfiguration();

  app.UseAuthorization();

  app.UseEndpoints(endpoints =>
  {
     endpoints.MapRazorPages();
  });
}

Herstart de applicatie en je zult zien dat de melding op het scherm nu de waarde is die je eerder in de App Configuration service hebt opgevoerd.

Ga in de Azure portal naar de eerder aangemaakte App Configuration service en pas hier het item met de key ‘BLOG:blogSection:message’ aan. Ga terug naar de applicatie in de browser en ververs de pagina. Je zult zien dat er niets verandert.

Ga weer terug naar de App Configuration service en verander het item met de key ‘BLOG:blogSection:configVersion’ naar bijv. “0.2”. Ga wederom terug naar de applicatie in de browser en ververs de pagina weer. Je zult zien dat de melding op het scherm verandert.

Nu hebben we een applicatie die zijn configuratie uit een extern systeem uitleest. Met relatief eenvoudige stappen ben je dus in staat om een applicatie van de standaard manier van configuratie inlezen om te zetten naar het uitlezen van configuration uit een Azure App Configuration service zonder daarvoor de hele applicatie over de kop te moeten gooien. De aanpassingen blijven beperkt tot de Program.cs en de Startup.cs, de rest van de applicatie kan blijven zoals hij is.

Documentatie

Meer informatie over de Azure Application Configuration Service

Quickstart ASP.NET Core Web app

Code Demo applicatie op GitHub