Hola de nuevo! Aquí estamos, intentando descubrir algunas funcionalidades “ocultas” de .Net. Hace unos días escribí un artículo sobre como utilizar múltiples Startups en tus aplicaciones MVC.

Siguiendo en la misma línea, hoy veremos otra forma de ordenar la configuración de servicios y del pipeline, esta vez sin la necesidad de tener clases Startup.cs separadas.

Situación actual

Como es habitual, realizamos la configuración de todos los servicios y la configuración del pipeline de nuestra aplicación en la clase Startup.cs. Aquí vemos un ejemplo simple pero muy frecuente:

public class Startup
{
    private readonly IWebHostEnvironment env;

    public Startup(IWebHostEnvironment env) =>
        this.env = env;

    public void ConfigureServices(IServiceCollection services)
    {
        if(env.IsDevelopment())
            services.AddDistributedMemoryCache();
        else
            services.AddDistributedRedisCache(options => { });

        // More services here...
    }

    public void Configure(IApplicationBuilder app)
    {
        if (env.IsDevelopment()) 
            app.UseDeveloperExceptionPage();

        // UseRouting(), UseAuthorization(), ...
    }
}

En el caso de que el ambiente sea Development, configuramos un caché distribuido en memoria, caso contrario utilizamos Redis. De la misma forma, sólo mostramos la página de excepciones si el ambiente es Development.

Requerimiento

Queremos hacer nuestro Startup.cs mas limpios y declarativos, eliminando así la necesidad de tener que validar si el ambiente es Development o no. Idealmente, queremos apoyarnos en el framework sin tener que escribir nuestra propia implementación.

Otra funcionalidad oculta

Sabemos que nuestra clase Startup.cs debe contener dos métodos, ConfigureServices para registrar los servicios necesarias y Configure para configurar el pipeline. Estos métodos son llamados por el runtime en tiempo de ejecución en el momento adecuado.

Sin embargo una funcionalidad no muy conocida es que podemos tener distintos métodos con el nombre Configure{Ambiente}Services y Configure{Ambiente}. Luego el runtime ejecutará los que correspondan al ambiente actual.

Si no existe un método que corresponda al ambiente actual, ejecutará los métodos por defecto ConfigureServices y Configure.

Implementación

El cambio es mu simple, crearemos dos nuevos métodos, ConfigureDevelopmentServices y ConfigureDevelopment, y configuraremos todos los servicios correspondientes al ambiente de desarrollo específicamente:

public class Startup
{
    // Default
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDistributedRedisCache(options => { });

        // More services...
    }

    // Development
    public void ConfigureDevelopmentServices(IServiceCollection services)
    {
        services.AddDistributedMemoryCache();

        // More services...
    }

    //Default
    public void Configure(IApplicationBuilder app)
    {
        // UseRouting(), UseAuthorization(), ...
    }

    // Development
    public void ConfigureDevelopment(IApplicationBuilder app)
    {
        app.UseDeveloperExceptionPage();

        // UseRouting(), UseAuthorization(), ...
    }
}

Para probarlo, solo debemos establecer algunos breakpoints y ejecutar la aplicación en el ambiente de desarrollo. El framework hará el resto!

Un poco de refactorización

Podemos tener una solución mucho más elegante si creamos un método ConfigureBaseServices conde pongamos todos los servicios comunes, de forma que no tengamos que repetir código (algo que odiamos!):

// Default
public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedRedisCache(options => { });
    ConfigureBaseServices(services);
}

// Development
public void ConfigureDevelopmentServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache();
    ConfigureBaseServices(services);
}

// Base
private void ConfigureBaseServices(IServiceCollection services)
{
    // Common services...
}

Conclusión

En este post vimos cómo aplicando una pequeña refactorización en la clase Startup.cs podemos sacar provecho de una funcionalidad del framework poco conocida que nos permite configurar los servicios y el pipeline según el ambiente en el que se ejecute la aplicación.