In this post, we are going to see how to correctly import this configuration in different environments, or even for our local development.
For this example, I am going to use the sample project I have on GitHub, which we have seen throughout this series.
Index
1 - What is configuration in .NET?
When designing applications, we have elements that can vary depending on the environment in which we are deploying the application.
For example, when deploying the application to the testing server, we want to use the testing server's configuration, such as the database. However, if we deploy to production, we want everything to point to production.
We want this configuration to be different at runtime, which is why we must handle it this way and not by putting #IF Debug
everywhere as it was done in the past.
1.1 - Default configuration in ASP.NET
When we create a new project in visual studio, such as an API project, it comes with an initial configuration or framework for the application, which provides the minimum requirements for the vast majority of applications.
For example, the dependency injection container so we can apply dependency injection, configuration for logging
or the host
in which it will run.
This configuration can be easily extended. Additionally, an options pattern for configuration is also specified in the framework. In the next post, we will see how to use it.
1.2 - Accessing configuration in .NET
The configuration comes in the Microsoft.Extensions.Configuration
package, which is available in Nuget, but for ASP.NET Core applications, it is included by default.
Having the configuration separated in a package means we can create configurations even in projects that are not ASP.NET Core, such as console apps or tests.
1.3 - How configuration is defined in .NET
Configuration for an application is done as a set of Key-Value
pairs, where the Value
can also be another Key-Value
pair.
The Key
is used to identify which part of the configuration we are in, and so we can access it at runtime. The value
contains the data the key
refers to.
For example, conexionSQL = “Server=127.0.0.1;Port=3306;Database=webpersonal”
.
- Note: configuration files also support
boolean
andint
types for the value.
1.3.1 - Hierarchical organization of configuration
As I mentioned before, a Key-Value
can contain another key-value
in the value
. To access a key that is "inside" we use the following syntax ParentKey:ChildKey = "childValue"
, which allows us to have the configuration properly structured:
{
"database": {
"read": {
"connectionString": "Server=127.0.0.1;Port=3306;Database=webpersonal"
}
}
}
In this case, to access the connection value, we will use the key database:read:connectionString
.
2 - Defining configuration in .NET
To define this configuration in .NET, we use a .json
file and in ASP.NET Core we do this in the appsettings.json
file, which is included by default when creating the project. It contains the following configuration:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
As you can see, it is a configuration related to logging
and AllowedHost
, which we will explain in another post.
What we want to do in this case is update the code so that it contains the database configuration instead of having it hardcoded in the ConfigureServices
method, as we have right now.
public void ConfigureServices(IServiceCollection services)
{
//other code
services
.AddScoped<DbConnection>(x => new MySqlConnection("Server=127.0.0.1;Port=3306;Database=webpersonal;Uid=webpersonaluser;password=webpersonalpass;Allow User Variables=True"));
}
The first step in our goal is very simple: move this configuration to the appsettings.json
file, which will look like this:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"database": {
"Server": "127.0.0.1",
"Port": 3306,
"DatabaseName": "webpersonal",
"User": "webpersonaluser",
"Password": "webpersonalpass",
"AllowUserVairables": true
},
"AllowedHosts": "*"
}
- Note: in an ideal scenario, this configuration should be stored in what are called
Secrets
, but for this example, it is sufficient. If you use secrets, you will need the URL that gives you access to the secrets in the configuration.
As a final point, IConfiguration
provides an extension method called .GetConnectionString(name)
by default, which will look for the connection you specify within ["ConnectionStrings:name"]
.
2.1 - Reading configuration in .NET with IConfiguration
Now what we have to do is read this configuration. For this, .NET provides us with the interface IConfiguration
, which is included in the dependency injection container, so we can include it in the constructor of our startup.cs
class.
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
//Other methods
}
And once we have access to IConfiguration
we access its values using the key
. For example, to access the server
of our database, we write IConfiguration["database:server"]
, and the same for the rest of the elements.
In my case, I created a class which will return the formatted URL
public static class Database
{
public static string BuildConnectionString(IConfiguration config)
{
StringBuilder sb = new StringBuilder();
sb.Append($"Server={config[\"database:server\"]};");
sb.Append($"Port={config[\"database:Port\"]};");
sb.Append($"Database={config[\"database:DatabaseName\"]};");
sb.Append($"Uid={config[\"database:User\"]};");
sb.Append($"password={config[\"database:Password\"]};");
if (config.GetValue<bool>("database:AllowUserVairables") == true)
{
sb.Append("Allow User Variables=True;");
}
return sb.ToString();
}
}
As you can see, for the last parameter I am accessing it differently. This is because IConfiguration
contains a method to automatically cast, but access is just as before, through the Key
.
When a value is not found, the default value is used. When we do GetValue<T>
, it uses the default value of T
, and when we access by key directly, it will return null
, the default value for string
.
3 - Accessing sections in IConfiguration
The configuration in .NET allows us to use what is called a section
, which lets us access different sections individually within the configuration. To do this, we just need to run the following code
IConfigurationSection section = config.GetSection("Database");
One of the main advantages is that when we specify a section, we don't have to write out the whole key again for that section.
And the code looks like the following:
public static string BuildConnectionString(IConfiguration config)
{
IConfigurationSection section = config.GetSection("Database");
StringBuilder sb = new StringBuilder();
sb.Append($"Server={section.GetValue<string>(\"server\")};");
sb.Append($"Port={section.GetValue<int>(\"Port\")};");
sb.Append($"Database={section.GetValue<string>(\"DatabaseName\")};");
sb.Append($"Uid={section.GetValue<string>(\"User\")};");
sb.Append($"password={section.GetValue<string>(\"Password\")};");
if (section.GetValue<bool>(\"AllowUserVairables\") == true)
{
sb.Append("Allow User Variables=True;");
}
return sb.ToString();
}
4 - Converting IConfiguration to an Object in .NET
Our journey with configuration does not end here, as we also have the possibility of converting a section of our configuration into a strongly typed object, in our case, a class, which allows us to reduce the number of hardcoded elements in the code.
The first thing we need is a class that is 1 to 1 with the configuration we want to use. For our database example, it will be the following:
public class DatabaseSettings
{
public string Server { get; set; }
public int Port { get; set; }
public string DatabaseName { get; set; }
public string User { get; set; }
public string Password { get; set; }
public bool AllowUserVairables { get; set; }
}
Then we only need to use the method provided by IConfiguration
called Bind
and pass the key and an instance of the object to map:
public static string BuildConnectionString(IConfiguration config)
{
DatabaseSettings dbSettings = new DatabaseSettings();
config.Bind("database", dbSettings);
StringBuilder sb = new StringBuilder();
sb.Append($"Server={dbSettings.Server};");
sb.Append($"Port={dbSettings.Port};");
sb.Append($"Database={dbSettings.DatabaseName};");
sb.Append($"Uid={dbSettings.User};");
sb.Append($"password={dbSettings.Password};");
if (dbSettings.AllowUserVairables == true)
{
sb.Append("Allow User Variables=True;");
}
return sb.ToString();
}
Now we can access the variables directly using the type, which is so much better.
5 - Overwriting configuration in .NET
When we develop code, it's not for it to run on our own machine, but rather to run on a server, commonly on several servers, and for each of them (dev, uat, production), the configuration parameters will be different.
In the example we have seen throughout the code, the database is located on localhost, but when we deploy to production it will be "EnProduccion
" and the password will be changed to something more complex.
ASP.NET Core gives us the ability to define configuration that will be different per environment.
- Note: environments in .NET are identified by the environment variable "
ASPNETCORE_ENVIRONMENT
", which can be set in both Visual Studio and Rider in the project properties.
The way we can create multiple configurations is by using .json
files. We have already seen how to use appsettings.json
for our configuration. What we can do is create another one for production, which must be in the same folder, and will be called appsettings.production.json
:
And as we can see, Visual Studio places it "together" with the other appsettings
files we have.
Now we simply need to overwrite the configuration that we want to change, in our case, the server and password:
{
"database": {
"Server": "EnProduccion",
"Password": "C0nTr$Se!Dif1c1L"
}
}
And in the code we don't have to change anything. If we deploy to production (ASPNETCORE_ENVIRONMENT = production
), it will read the new values:
Conclusion
In this post, we saw what configuration is in .NET
How to access and set configuration in our .NET applications
Different ways to access configuration in .NET using IConfiguration
Convert configuration to an object
Overwrite configuration per environment in .NET
If there is any problem you can add a comment bellow or contact me in the website's contact form