Inyección de Dependencias en ASP.NET Core – III
En las dos entradas anteriores (la primera, y la segunda) sobre Inyección de Dependencias en ASP.NET Core, veíamos como funcionaba a rasgos generales los diferentes ciclos de vida existentes: Singleton, Transient y Scoped.
En esta ocasión, vamos a trabajar con cada uno de ellos de forma práctica.
De esta forma, entenderemos mucho mejor su comportamiento.
Para ello, voy a partir de un proyecto Web con una vista (en realidad estoy reutilizando el mismo proyecto todo el momento) que estará en foo/index.
He creado una interfaz de nombre IData con el siguiente código:
public interface IData { string Get(); void Append(string data); void Clear(); }
De la misma manera, he creado una clase de nombre Data que implementa la interfaz con el siguiente código:
public class Data : IData { private string _data = String.Empty; public void Append(string data) { _data += data; } public string Get() { return _data; } public void Clear() { _data = String.Empty; } }
En el método ConfigureServices de la clase Startup he configurado la interfaz y la implementación de la interfaz como Singleton de momento y de la siguiente forma (luego repasaremos esta parte para simular los diferentes comportamientos existentes en IoC):
services.AddSingleton<IData, Data>();
Adicionalmente, he creado un ViewModel de nombre DataViewModel con el siguiente código:
public class DataViewModel { public DataViewModel(string info) { Info = info; } public string Info { get; } }
Como vemos hasta ahora todo muy básico, pero suficiente creo yo para explicar y comprender perfectamente el funcionamiento de cada uno de los diferentes ciclos de vida de IoC.
A continuación, modificaré la vista que tengo en Foo/Index.cshtml.
Dentro escribiré el siguiente código:
@model AspNetCoreProject.Models.DataViewModel @{ ViewData["Title"] = "View"; } <h1>Foo</h1> <u>Text:</u><br /> <i>@Model.Info</i>
Llegados a este punto, lo único que debemos hacer ahora es preparar nuestro controlador.
Mi controlador FooController tendrá un código similar al siguiente:
public class FooController : Controller { private readonly IData _data; public FooController(IServiceProvider serviceProvider, IData data) { var myService = new MyService(serviceProvider); System.Threading.Thread.Sleep(2500); _data = data; _data.Append($"{nameof(FooController)} " + DateTime.Now + " | "); } public IActionResult Index() { var viewModel = new DataViewModel(_data.Get()); return View(viewModel); } } public class MyService { public MyService(IServiceProvider serviceProvider) { var data = (IData)serviceProvider.GetService(typeof(IData)); data.Append($"{nameof(MyService)} " + DateTime.Now + " | "); } }
Aquí voy a explicar un poco el código que he preparado para que se entienda bien el propósito del mismo.
El constructor de nuestro controlador recibirá dos parámetros que .NET Core se encargará de pasarnos y de resolver por nosotros.
Uno de los parámetros es IServiceProvider, y lo utilizaremos para resolver IData dentro de un servicio que utilizaremos para similar/forzar los diferentes comportamientos de los ciclos de vida.
Otro de los parámetros es IData, que en el caso del controlador, sólo será usado por este.
El servicio MyService resolverá por otro lado IData.
Para rizar un poco más el rizo, dentro del controlador he forzado igualmente un retardo de 2 segundos y medio.
Es simplemente una simulación de un proceso que lleva un determinado coste de tiempo de proceso y para que veamos en pantalla que el código efectivamente se ejecuta en el servicio y en el controlador, aunque el nombre del servicio y del controlador ayudará a saber que la información que mostramos en pantalla dentro de la vista, pertenece a uno y/u a otro.
Así que el objetivo es el siguiente:
Cuando estemos en un ciclo de vida Singleton, al resolver IData se creará una única instancia de Data y permanecerá durante lo que dure la aplicación Web.
Por lo tanto, cuando accedamos a la vista y la recarguemos, el contenido que se introduce en el controlador y en el servicio, se irá duplicando, y lo podremos ver en la vista.
Cuando estemos en un ciclo de vida Transient, al resolver IData se creará una instancia de Data en el servicio en primer lugar, y en el controlador después.
Sin embargo aquí, sólo se pintará la información del último que lo usó, es decir, en este caso el controlador, ya que lo que hace Transient es crear una instancia de Data cada vez que lo resolvemos.
En el caso de Scoped, el ciclo de vida es por request, así que al resolver IData se creará una instancia de Data que será compartida durante la request.
De esta forma, el servicio introducirá en primer lugar algo de información, y luego el controlador que es el que llamamos en segundo lugar reutilizará Data porque está dentro de la misma request.
Si actualizamos el navegador, el ciclo se repetirá, pero al ser una nueva request, el objeto Data se creará como nuevo.
Para llevar a cabo todas estas operaciones, me iré a la clase Startup y añadiré dentro de ConfigureServices una de las siguientes resoluciones según quiera ver el comportamiento de Singleton, Transient o Scoped.
services.AddSingleton<IData, Data>();
services.AddTransient<IData, Data>();
services.AddScoped<IData, Data>();
(Las tres a la vez no)
En ejecución, la aplicación se comportará de la siguiente forma (en todas ellas he recargado la vista varias veces):
Singleton
La clase Data se creará la primera vez que se solicite y estará en memoria mientras la aplicación Web funcione, con independencia del número de veces que hacemos una petición a la vista.
Transient
La clase Data se creará cada vez que se pide su resolución, así que según el ejemplo, se creará en el servicio al principio, y en el controlador después.
Como se llama en dos sitios, se crearán dos instancias de Data «machacando» una con la otra, por lo que sólo se pintará la información del último que la llamó, en nuestro ejemplo el controlador.
Scoped
La clase Data se creará cada vez que se inicia una petición o request, por lo que la instancia de Data será compartida dentro de la request. Como aquí el primero que la crea es el servicio, el controlador reutilizará la instancia creada por el servicio.
Espero que este ejemplo ayuda a comprender mejor todo el comportamiento y ciclo de vida en IoC en general, y en ASP.NET Core en particular.
Happy Coding!
One Responseso far
Felicitarte por los artículos la verdad que 10 puntos mas claro imposible.