En este post vamos a ver cómo importar esta configuración de forma correcta en los diferentes entornos, o incluso para nuestro desarrollo local.
Para este ejemplo voy a utilizar el proyecto de ejemplo que tengo en GitHub, que hemos visto a lo largo de esta serie.
Índice
1 - Qué es la configuración en .NET?
Cuando diseñamos aplicaciones tenemos elementos que pueden variar dependiendo en qué entorno estamos desplegando la aplicación.
Por ejemplo, cuando desplegamos la aplicación en el servidor de pruebas queremos utilizar la configuración del servidor de pruebas, como puede ser la base de datos. En cambio, si desplegamos en producción, queremos que todo esté apuntando a producción.
Esta configuración deseamos que sea diferente en tiempo de ejecución por eso debemos hacerlo así y no poniendo #IF Debug
por todas partes como se hacía en antaño.
1.1 - Configuración por defecto en ASP.NET
Cuando creamos un nuevo proyecto en visual studio como puede ser un proyecto API este nos viene con una configuración o framework inicial de la aplicación, la cual van a ser los requerimientos mínimos para la gran mayoría de aplicaciones.
Por ejemplo, el contenedor de dependencias para poder aplicar inyección de dependencias, Configraución para el logging
o el host
en el que va a correr.
Esta configuración se puede extender muy fácilmente, Adicionalmente un patrón para la configuración, denominado “options pattern
” tambien viene especificado en el framework, En el siguiente post veremos cómo utilizarlo.
1.2 - Acceder a la configuración en .NET
La configuración viene en el paquete Microsoft.Extensions.Configuration
el cual está disponible en Nuget, pero para las aplicaciones ASP.NET Core, viene especificado por defecto.
Tener la configuración separada en un paquete significa que podemos crear configuraciones incluso en proyectos que no son ASP.NET Core, como por ejemplo en aplicaciones de consola, o tests.
1.3 - Cómo se define la configuración en .NET
La configuración de una aplicación se hace un un conjunto de Key-Value
donde el Value
puede ser otro Key-Value
.
La Key
se utiliza para identificar en qué parte de la configuración estamos, y así poder acceder en tiempo de ejecución. y el valor
contiene los datos a los que hace referencia dicha key
.
Por ejemplo conexionSQL = “Server=127.0.0.1;Port=3306;Database=webpersonal”
.
- Nota: los ficheros de configuración también soportan tipos
boolean
eint
para el valor.
1.3.1 - Organización jerárquica de la configuración
Como he indicado en el punto anterior, un Key-Value
puede contener en el value
otro key-value
para acceder a la key que está “dentro” lo hacemos a través de la siguiente sintaxis KeyPadre:KeyHija = “valorHijo”
, lo que nos permite tener la configuración estructurada correctamente:
{
"database": {
"read": {
"connectionString": "Server=127.0.0.1;Port=3306;Database=webpersonal"
}
}
}
En este caso para acceder al valor de la conexión utilizaremos la siguiente key database:read:connectionString
.
2 - Definir la configuración en .NET
Para definir esta configuración en .NET lo hacemos a través de un fichero .json
y en ASP.NET Core lo hacemos en el fichero appsettings.json
, que nos viene por defecto al crear el proyecto. El cual contiene la siguiente configuración:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Como vemos es una configuración relacionada con el logging
y AllowedHost
el cual veremos lo que es en otro post.
Lo que queremos hacer en este caso es, actualizar el código para que contenga la configuración de la base de datos en vez de tenerla hardcoded en el método ConfigureServices
como la tenemos ahora mismo.
public void ConfigureServices(IServiceCollection services)
{
//otro código
services
.AddScoped<DbConnection>(x => new MySqlConnection("Server=127.0.0.1;Port=3306;Database=webpersonal;Uid=webpersonaluser;password=webpersonalpass;Allow User Variables=True"));
}
El primer paso de nuestro objetivo es muy sencillo, mover esta configuración al fichero appsettings.json
el cual va a quedar así:
{
"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": "*"
}
- Nota: en un escenario ideal esta configuración tendría que estar en lo que se denominan
Secrets
, pero para el ejemplo nos sirve, en el caso de utilizar secrets, en la configuración necesitarás la url que te de acceso a los secrets.
Como punto final, IConfiguration
provee un extension method llamado .GetConnectionString(nombre)
por defecto, que buscará la conexión que le indiques dentro de [“ConnectionStrings:nombre”]
.
2.1 - Leer configuración en .NET con IConfiguration
Ahora lo que debemos hacer es leer esta configuración. Para ello .NET nos provee de la interfaz IConfiguration
, la cual esta includia en el contenedor de dependencias, por lo que podemos incluirla en el constructor de nuestra clase startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
//Otros métodos
}
y una vez tenemos acceso a IConfiguration
accedmos a sus valores a través de la key
, por ejemplo, para acceder al server
de nuestra base de datos escribiremos IConfiguration[“database:server”]
y lo mismo para el resto de elementos.
En mi caso, he creado una clase la cual va a devolverme la url formateada
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();
}
}
Como puedes observar para el último parámetro estoy accediendo de forma diferente. Esto es porque IConfiguration
contiene un método para hacer casting automáticamente, pero el acceso es igual que antes, a través de la Key
.
Cuando un valor no es encontrado, se utiliza el valor por defecto, cuando hacemos GetValue<T>
utiliza el valor por defecto de T
, y cuando accedemos por key directamente nos devolverá null
, el valor por defecto de string
.
3 - Acceder a secciones en IConfiguration
La configuración en .NET nos permite utilizar lo que se denomina section
lo cual nos permite acceder a diferentes secciones de forma individual dentro de la configuración. Para ello únicamente debemos de ejecutar el siguiente código
IConfigurationSection section = config.GetSection("Database");
Una de las grandes ventajas es que cuando indicamos una sección, no tenemos que volver a escribir toda la parte de la key que está en esa sección.
Y el código se ve de la siguiente manera:
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 - Convertir IConfiguration en un Objeto en .NET
Nuestra andadura con la configuración no acaba aquí, sino que tambien disponemos de la posibilidad de convertir una sección de nuestra configuración en un objeto tipado, en nuestro caso, una clase, lo cual nos permite reducir el numero de elementos hardcoded en el código.
Lo primero que necesitamos es una clase que sea 1 a 1 con la configuración que queremos utilziar, para nuestro ejemplo de la base de datos será la siguiente:
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; }
}
Y posteriormente únicamente debemos utilizar el método que nos provee IConfiguration
llamado Bind
y le pasamos la key y una instancia del objeto a mapear:
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();
}
Ahora podemos acceder a las variables utilizando directamente el tipo, lo cual es mucho mejor.
5 - Sobreescribir configuración en .NET
Cuando desarrollamos código, no lo hacemos para que se ejecute en nuestro ordenador, sino que lo hacemos para que se ejecute en un servidor, comúnmente lo hará en varios, y para cada uno de ellos (dev, uat, producción) los parámetros de la configuración serán diferentes.
En el ejemplo que hemos visto durante todo el código, la base de datos está ubicada en localhost, pero cuando despleguemos a producción será "EnProduccion
" así como la contraseña cambiará a una más compleja.
ASP.NET Core nos da la posibilidad de definir configuración que va a ser diferente por entorno.
- Nota: los entornos en.NET se identifican con la variable de entorno "
ASPNETCORE_ENVIRONMENT
", la cual se puede definir tanto en visual studio como en rider en las propiedades del proyecto.
La forma en la que podemos crear múltiples configuraciones es utilizando los ficheros .json
, hemos visto como utilizar appsettings.json
para nuestra configuración, lo que podemos hacer es crear otro para producción, el cual, tiene que estar ubicado en la misma carpeta, y se llamará appsettings.production.json
:
Y como vemos visual studio nos lo ubica “junto” a los otros ficheros appsettings
que tengamos.
Ahora únicamente debemos sobreescribir la configuración que deseamos modificar, en nuestro caso, el servidor y la contraseña:
{
"database": {
"Server": "EnProduccion",
"Password": "C0nTr$Se!Dif1c1L"
}
}
Y en el código no tenemos que tocar nada, si desplegamos a producción (ASPNETCORE_ENVIRONMENT = production
) nos leerá los nuevos valores:
Conclusión
En este post hemos visto que es la configuración en .NET
Cómo acceder y configurar la configuración en nuestras aplicaciones .NET
Diferentes formas de acceder a la configuración en .NET a través de IConfiguration
Convertir la configuración en un objeto
Sobrescribir la configuración por entorno en .NET