Basics of accessing the Configuration in .NET Web App
Tags: dotnet, beginners, csharp
Published: 2023-09-19 15:41:35


In the previous post we talked about Configuration Sources and Configuration Provider - two different concepts but intertwined together.

We also spoke in brief about how this 

`var builder = WebApplication.CreateBuilder(args);` 

wires up the defaults for our apps (in terms of logging, dependency injections, configuration, etc. )

We also established that `appsettings.json` and `appsettings.{environment}.json` are amongst the default configuration sources and are loaded by default through the json configuration provider. In addition to the defaults, we can also add our own custom sources and of course they would need their own custom configuration providers defined (i.e. you need to write your [own](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-7.0#custom-configuration-provider)).

Let's look at how you can access the configuration within your application from the `builder`

Say you have an appsettings.json file

```yaml
{
    "ConnectionStrings": {
        "SqliteConnection": "Filename=:memory",
        "PostgresConnection": "Post gres connection string goes here"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "Message": "Roofus Doofus !!"
    "Title" : {
        "HomePage" : "Stockholm i mitt hjärta"
       }
}
```

And you want to access the **_Message_** and the **_ConnectionStrings_** in your application. Since appsettings.json is shipped by the .NET framework and loaded by default, we don't have to worry about that part.

We can access the Message config from the file ,in its most basic form through an indexer on the `Configuration` collection provided by the `builder ` object.

`var message = builder.Configuration["Message"];`

The config which we want to access becomes the key here

The connection string for _Sqlite_ can be retrieved as :

```cs
var sqlConnectionString = builder.Configuration.GetConnectionString("SqliteConnection");
```

If you closely observe **_SqliteConnection_** is a level below (or nested within) **_ConnectionStrings_** . Usually, if we have to access a nested config then we do it through the "**:**" notation. 
But the GetConnectionString is an extension method provided by the framework and it does the following behind the scenes

```cs
   public static string GetConnectionString(this IConfiguration configuration, string name)
        {
            return configuration?.GetSection("ConnectionStrings")?[name];
        }
```

But since the DI is one of the key highlights of .NET framework, we can inject the configuration as a dependency which will be resolved by .NET framework itself

The `Configuration ` on the `builder` is of type `ConfigurationManager` which is a concrete implementation of `IConfigurationBuilder`, `IConfigurationRoot` 

`IConfigurationRoot` extends `IConfiguration` and as msdn highlights [here](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration#binding) , `IConfiguration` represents a set of key/value application configuration properties and that is what we are interested in.

So we inject a dependency of `IConfiguration` in our services if we want to access any configuration within it

Following is a snippet from a `RouteEndpoint` (an endpoint handler) from the minimal api scaffold

```cs
app.MapGet("/", ([FromServices] IConfiguration configuration) =>
{
   var sb = new StringBuilder();

   //By default the appsettings.json is loaded by the json configuration provider when the host is built

   //if we have to access the configuration, we can do it via the IConfiguration interface

   //The hierarchical structure in the json file is flattened like this. you access the children through ":" notation

   var title= configuration.GetValue<string>("Title:HomePage");
   sb.Append($"Homepage Title : {title} \n");

//If you try to access a non-existent key, it will set the default value

   var nonExistentKeyInConfiguration = configuration.GetValue<bool>("Title:Shopping");

   sb.Append($"Trying to access a non-existent key from the configuration  will set the default value of T on GetValue<T>: {nonExistentKeyInConfiguration} \n");


//And if you try to cast the config to an invalid cast, e.g. our Title config is given as a string and we try to cast it to int then we will get an exception

   try
   {
      var inconsistentTypeCasting = configuration.GetValue<int>("Title:HomePage");

   }
   catch (Exception e)
   {
      sb.Append($"Inconsistent type casting of a config value while retrieving it will throw an exception of {e.InnerException?.Source}\n");

   }

   return sb.ToString();
});
```

Lets look at a more complex config and how to map it to a csharp complex type
You have the following in your `appsettings.json`

```json
    "Pet": {
        "Name": "Roofus",
        "Description": "I climb through people's house fences and scare them'",
        "IsFriendly": false,
        "Age": 5
    }
```

For this you would create a complex type say Pet :
(OBS - The type name can be different than the config name, it does not have to match)

```cs
 public class Pet
 {
  public string Name { get; set; } = string.Empty;
  public string Description { get; set; } = string.Empty;
  public bool IsFriendly { get; set; }
  public int Age { get; set; }
 }
```

Going back to our json config,

```json
    "Pet": {
        "Name": "Roofus",
        "Description": "I climb through people's house fences and scare them'",
        "IsFriendly": false,
        "Age": 5
    }
```

"Pet" is referred as a **_section_** and we bind the "section" calling the Bind on the `IConfiguration` injected in our service

```cs

 //we create the pet object where we want to bind the configuration
   var petUsingBind = new Pet();

   //Call the bind with the section name and pass the object created.
   configuration.Bind("Pet", petUsingBind);

```

To wrap up , 
In the previous post we saw that the default configurations are loaded in a particular order and the order in which the configuration sources are loaded determines if a config overrides the other i.e. the order matters !


![Config Sources in Order](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvbgsw2jvv82xv2o36vf.jpg)

Well, the good news is we can override the default behaviour and define our own through the `ConfigureAppConfiguration `available on the `host `exposed through `builder`

```cs
builder.Host.ConfigureAppConfiguration((ctx, configBuilder) =>
{
 //we clear all the config sources previously read in the default order 

 configBuilder.Sources.Clear();

 var env = ctx.HostingEnvironment;

 configBuilder.AddEnvironmentVariables("ASPNETCORE_");
 //we are only reading the appsettings.json file here. explicitly skipping the environment specific
 configBuilder.AddJsonFile("appsettings.json",optional:true,reloadOnChange:true);

//We skip the user secrets
//We skip the command line arguments as well

});

```

We will look at the options pattern in the next post

**References**

The msdn documentation on[ fundamentals of configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-7.0) is really good and has all you need to know information. Please check it out to explore more in this area

[Customization through `ConfigureAppConfiguration`](https://www.jondjones.com/programming/aspnet-core/how-to/essential-net-6-app-settings-tips-master-developer-and-environment-specific-settings/)

Further [reading ](https://andrewlock.net/exploring-dotnet-6-part-1-looking-inside-configurationmanager-in-dotnet-6/) on ConfigurationManager (type of builder.Configuration)