L'uscita di ASP.NET Core ha portato a una maggiore diffusione dell'utilizzo della DI (Dependency Injection) ma spesso senza che lo sviluppatore sappia la sua reale utilità o potenzialità.
Dependency Injection, uno dei design pattern della programmazione OOP. Una best-practice dello sviluppo che può portare alla semplificazione del codice scritto, facilitare il disacoppiamento, e migliorare la testabilità.
In questa sessione vedremo cos'è la DI e come utilizzarla intelligentemente all'interno dei nostri progetti.
Slide dell'evento "XE One Day - Good code" tenuto il 15.09.2018.
Il codice è disponibile alla pagina dell'evento https://www.xedotnet.org/eventi/one-day-good-code/
2. Dependency injection (DI) è un design pattern della programmazione
orientata agli oggetti il cui scopo è quello di semplificare lo sviluppo e migliorare
la testabilità di software di grandi dimensioni.
Per utilizzare tale design pattern è sufficiente dichiarare le dipendenze che un
componente necessita (dette anche interface contracts). Quando il componente
verrà istanziato, un iniettore si prenderà carico di risolvere le dipendenze
(attuando dunque l'inversione del controllo). Se è la prima volta che si tenta di
risolvere una dipendenza l'injector istanzierà il componente dipendente, lo
salverà in un contenitore di istanze e lo restituirà. Se non è la prima volta, allora
restituirà la copia salvata nel contenitore. Una volta risolte tutte le dipendenze, il
controllo può tornare al componente applicativo.
Il pattern Dependency Injection coinvolge almeno tre elementi:
• una componente dipendente,
• la dichiarazione delle dipendenze del componente, definite come interface
contracts,
• un injector (chiamato anche provider o container) che crea, a richiesta, le
istanze delle classi che implementano delle dependency interfaces.
15/09/2018 2
Dependency injection pattern
(fonte: https://en.wikipedia.org/wiki/Dependency_injection)
3. Dependency injection (DI) è un design pattern della programmazione
orientata agli oggetti il cui scopo è quello di semplificare lo sviluppo e migliorare
la testabilità di software di grandi dimensioni.
Per utilizzare tale design pattern è sufficiente dichiarare le dipendenze che
un componente necessita (dette anche interface contracts). Quando il
componente verrà istanziato, un iniettore si prenderà carico di risolvere le
dipendenze (attuando dunque l'inversione del controllo). Se è la prima volta
che si tenta di risolvere una dipendenza l'injector istanzierà il componente
dipendente, lo salverà in un contenitore di istanze e lo restituirà. Se non è la
prima volta, allora restituirà la copia salvata nel contenitore. Una volta risolte
tutte le dipendenze, il controllo può tornare al componente applicativo.
Il pattern Dependency Injection coinvolge almeno tre elementi:
• una componente dipendente,
• la dichiarazione delle dipendenze del componente, definite come interface
contracts,
• un injector (chiamato anche provider o container) che crea, a richiesta, le
istanze delle classi che implementano delle dependency interfaces.
15/09/2018 3
Dependency injection pattern
(fonte: https://en.wikipedia.org/wiki/Dependency_injection)
4. • https://deviq.com/inversion-of-control/
• Inversion of Control (IoC or IOC) describes a system
that follows the Hollywood Principle ( “Don’t Call Us,
We’ll Call You.” ). That is, flow of control within the
application is not controlled by the application itself, but
rather by the underlying framework. Typically in such an
architecture, the application is written such that it ties
into the application framework by handling framework
events or plugging in to framework extension points.
• An IOC Container, also known as a Dependency
Inversion (DI) container, is a specialized factory used to
facilitate dependency injection.
15/09/2018 4
Inversion of Control
5. public class HomeController : Controller
{
private readonly SqlDataAccess _dataAccess;
public HomeController()
{
var connectionString = ConfigurationManager.ConnectionStrings["xe"].ConnectionString;
this._dataAccess = new SqlDataAccess(connectionString);
}
public IActionResult Index()
{
var speakers = this._dataAccess.GetSpeakers();
return View(speakers);
}
}
15/09/2018 5
Prima
6. public class HomeController : Controller
{
private readonly IDataAccess _dataAccess;
public HomeController(IDataAccess dataAccess)
{
this._dataAccess = dataAccess;
}
public IActionResult Index()
{
var speakers = this._dataAccess.GetSpeakers();
return View(speakers);
}
}
15/09/2018 6
Dopo
7. Riduzione delle dipendenze (dirette)
il codice non è legato ad una precisa implementazione di
una sua dipendenza, ma (solitamente) ad un'interfaccia,
favorendo così il disaccoppiamento
Codice più riutilizzabile
riducendo le dipendenze si facilita il riuso del codice in
contesti diversi
Codice più testabile
dipendendo da un'interfaccia, si è facilitati nella scrittura di
test, potendo iniettare dei mock/stub/fake delle
dipendenze
Codice più leggibile
si viene portati a scrivere classi focalizzate a risolvere il
singolo problema, semplificando di molto la scrittura e la
lettura del codice
15/09/2018 7
Dependency Injection Benefits
8. ASP.NET Core nasce con un proprio engine di Dependency
Injection
Utilizzato per iniettare tutti i service/factory necessari al
funzionamento di ASP.NET Core ed Entity Framework Core
Non si tratta di un'implementazione evoluta, ma espone
funzionalità che sono sufficienti alla maggioranza delle
applicazioni
15/09/2018 8
ASP.NET Core Dependency Injection
10. Le dipendenze/servizi possono venire create (e distrutte)
in base a regole definite nel DI Container.
Il DI Container si occupa di tenere traccia e risolvere le
dipendenze.
• Se un servizio ha a sua volta delle dipendenze, queste
vengono gestite a loro volta dal DI container.
• Se un servizio implementa IDisposable, il metodo
Dispose verrà chiamato automaticamente.
15/09/2018 10
Service Life Times
12. Scoped
La dipendenza viene create per "scope".
In un'applicazione web lo scope è la singola richiesta http.
15/09/2018 12
Service Life Times - Scoped
13. Singleton
La dipendenza viene creata la prima volta che viene
richiesta. Tutte le volte successive viene ritornata sempre
(e solo) l'istanza già creata.
15/09/2018 13
Service Life Times - Singleton
14. Le dipendenze vengono iniettate tramite il costruttore
della classe/controller/handler/…
15/09/2018 14
Constructor Injection
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public async Task OnGetAsync()
{
await _myDependency.WriteMessage("IndexModel.OnGetAsync created this message.");
}
}
15. Utilizzando l'attributo [FromService] prima di un
argomento di un metodo, fa si che questo venga
recuperato dal DI Container
Utile quando il servizio/dipendenza viene utilizzato da un
solo metodo del controller e si vuole evitare di farlo
risolvere ad ogni richiesta tramite dipendeza nel
costruttore
15/09/2018 15
Action Injection with FromServices
public IActionResult About([FromServices] IDateTime dateTime)
{
ViewData["Message"] = "Currently on the server the time is " + dateTime.Now;
return View();
}
16. In ASP.NET Core la DI funziona anche a livello di View,
dando la possibilità di iniettare direttamente le
dipendenze tramite la keyword @inject
15/09/2018 16
DI nelle View
@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<div>
<p>Total Items: @StatsService.GetCount()</p>
<p>Completed: @StatsService.GetCompletedCount()</p>
<p>Avg. Priority: @StatsService.GetAveragePriority()</p>
</div>
17. Possibilità di iniettare le dipendenze tramite proprietà
delle classi
Viene considerata come pratica da evitare in quanto
causa una problematica "Temporal Coupling"
• http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling/
ASP.NET Core non supporta direttamente la property
injection, ma è possibile utilizzarla sfruttando altri DI
Container.
15/09/2018 17
Property Injection
public class ValuesController : Controller
{
public IFooService FooService { get; set; }
[HttpGet]
public IActionResult Get()
{
// use FooService here
}
}
19. Esistono già delle implementazioni di terze parte che possono
andare a sostituire quella di default di ASP.NET Core:
• Autofac
• DryIoc
• Grace
• LightInject
• StructureMap
• Stashbox
• Unity
Utilizzo di altri DI container con
Microsoft.Extensions.DependencyInjection
15/09/2018 19
20. The Unity Container (Unity) is a lightweight, extensible
dependency injection container with optional support for
instance and type interception.
• https://github.com/unitycontainer/unity/
Registrazione delle dipendenze
Recupero di una dipendenza
Definizione di dipendenze nel file di configurazione
15/09/2018 20
Unity Container (Unity)
var container = new UnityContainer();
container.RegisterType<IDataAccess, SQLDataAccess>();
IDBAccess data = container.Resolve<IDataAccess>();
var container = new UnityContainer();
var section =(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
21. Autofac is an addictive Inversion of Control container for
.NET Core, ASP.NET Core, .NET 4.5.1+, Universal
Windows apps, and more.
• https://autofac.org/
15/09/2018 21
Autofac
var builder = new ContainerBuilder();
// Register individual components
builder.RegisterInstance(new TaskRepository()).As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogManager(DateTime.Now)).As<ILogger>();
// Scan an assembly for components
builder.RegisterAssemblyTypes(myAssembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
var container = builder.Build();
23. A partire dal .NET Framework 4.7.2 viene facilitato l'utilizzo della
Dependency Injection, anche se non al livello di ASP.NET MVC
• Annuncio del 30 Aprile 2018 - [ASP.NET] Support for ASP.NET
Dependency Injection
• "Support setter-based, interface-based and constructor-based
injection in web application project in Handler, Module, Page, User
control and Custom control."
• "Support setter-based and interface-based injection in web site
project in Handler, Module, Page, User controls and Custom controls."
• "Extensebility to support different dependency injection frameworks."
• Step1 - Implementare IServiceProvider
• Step2 - Valorizzare HttpRuntime.WebObjectActivator nel
Global.asax
15/09/2018 23
Utilizzare la DI in applicazioni WebForms
24. Su nuget è presente un package per avere la DI utilizzando
Unity senza doversi occupare di implementare manualmente
IServiveProvider
Install-Package Microsoft.AspNet.WebFormsDependencyInjection.Unity
15/09/2018 24
Microsoft.AspNet.WebFormsDependencyInjection.Unity
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
var container = this.AddUnity();
container.RegisterType<IPopularMovie, MovieManager>();
container.RegisterType<IMovieRepository, XmlMovieRepository>();
}
}
25. Si può utilizzare la DI anche in applicazioni che
utilizzando una versione del .NET Framework precedente
alla versione 4.7.2
Per iniettare le dipendenze si può creare (o utilizzare)
degli HttpModules, oppure una custom Factory che si
occupi di gestire le estensioni aspx
Si può utilizzare il pattern Service Locator per farsi dare
manualmente le dipendenze
15/09/2018 26
Utilizzare la DI in applicazioni WebForms
28. • Dependency injection in ASP.NET Core
• ASP.NET Core Dependency Injection Best Practices, Tips & Tricks
• Dependency Injection Benefits
• Use Dependency Injection In WebForms Application
• Using ASP.Net Webform Dependency Injection with .NET 4.7.2
• Announcing the .NET Framework 4.7.2
Links
15/09/2018 29