Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 5 de julio de 2022
ASP.NET Core

Una de las (muchas) cosas buenas que trajo ASP.NET Core (y .NET Core en general) sin duda ha sido la popularización de la inyección de dependencias y la filosofía de implementación de componentes desacoplados que expone en múltiples puntos.

Esto ha provocado que en nuestras aplicaciones sea ya habitual encontrar secciones de código dedicadas al registro de decenas o centenares de servicios usando los distintos ámbitos disponibles (scoped, singleton o transient). El problema es que esta abundancia de servicios y la asiduidad con la que registramos nuevos componentes o modificamos sus dependencias hace que se nos puedan pasar por alto detalles que pueden hacer que nuestra aplicación falle. Por ejemplo, es fácil que olvidemos registrar algún servicio, o que, por un despiste, inyectemos servicios en componentes registrados con ámbitos incompatibles.

Por defecto ASP.NET Core nos protege de estos errores automáticamente, realizando un chequeo básico del contenedor de servicios cuando lanzamos la aplicación en el entorno "Development". Se producirá una excepción durante la inicialización de la aplicación cuando:

  • En un servicio registrado se inyectan vía constructor servicios que no se han definido. Tiene sentido, puesto que la dependencia nunca podría ser satisfecha en tiempo de ejecución y provocaría un error en el momento de intentarlo.

  • Un servicio scoped deba ser creado desde el proveedor raíz. Esto ocurre, por ejemplo, cuando en un servicio registrado como singleton inyectamos un servicio scoped, dado que el singleton es creado una única vez y es ajeno a los ámbitos que usan los servicios scoped (en ASP.NET Core, la petición que está siendo procesada).

El hecho de que estas validaciones se realicen sólo cuando la aplicación corre en el entorno "Development" quiere decir que dejamos fuera otros entornos que quizás podrían resultar interesantes. "Production" quizás es especial porque estas comprobaciones requieren su tiempo y normalmente querremos que en producción la aplicación arranque lo antes posible. Pero en entornos de desarrollo personalizados, o incluso "Staging" podría venirnos bien este extra de seguridad para evitar males mayores.

En versiones de ASP.NET Core anteriores a la 6.0 podíamos activar estas comprobaciones manualmente en el código de inicialización de Program.cs:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        .UseDefaultServiceProvider(opt =>
        {
            opt.ValidateOnBuild = true;
            opt.ValidateScopes = true;
        });

En ASP.NET Core 6 la cosa cambia un poco, porque la configuración del host queda más escondida bajo las nuevas abstracciones del minimal hosting. Pero podemos conseguirlo de forma igualmente sencilla retocando el Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseDefaultServiceProvider(c=>
{
    c.ValidateScopes = true;
    c.ValidateOnBuild = true;
});

En general, creo que como medida de protección extra sería una buena idea introducir estas configuraciones en todos los entornos, quizás exceptuando "Production" en aplicaciones en las que minimizar el tiempo de inicio sea crítico. Por tanto, el código de arranque podría quedar como el siguiente:

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseDefaultServiceProvider(c =>
{
    if (!builder.Environment.IsProduction())
    {
        c.ValidateScopes = true;
        c.ValidateOnBuild = true;
    }
});

Pero ojo, no penséis que estaréis a salvo de cualquier metedura de pata en lo relativo al registro de servicios: estos chequeos no tienen en cuenta servicios genéricos, ni aquellos que no sean inyectados directamente al controlador (por ejemplo, los inyectados en métodos con [FromServices] o a través del service provider). Por defecto tampoco se tendrán en cuenta los controladores MVC, porque no son creados desde el inyector, a no ser que durante la inicialización así lo indiquemos:

...
builder.Services
    .AddControllersWithViews()
    .AddControllersAsServices(); // Add controllers to the DI container

Espero que os resulte de utilidad :)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!