Service Model

Il modello a servizi di .NET Core permette la registrazioni di tipi, factory o istanze che vengono poi utilizzate attraverso il Service Injecting.

Il pattern prevede 2 fasi principali. La prima è di registrazione dei servizi mentre la seconda è quella di esecuzione del programma in cui i servizi vengono utilizzati.

La necessità di questo pattern di definizione-implementazione permette di creare applicativi estendibili senza particolari sforzi e incremento di complessità generati dalla creazione di pattern ad-hoc per l’estindibilità di librerie condivise

Registrazione Serivizi

La registrazione dei servizi avviene tramite numerosi metodi di estensione di un’istanza di IServiceCollection. Un servizio può riferirsi ad un’interfaccia, ad una classe astratta o direttamente ad una classe implementata.

Il services model, però, suggerisce di utilizzare un sistema di interfaccia-implementazione per componenti esterne e, nel caso in cui fosse necessario, l’utilizzo diretto di classi implementate per servizi di utility presenti negli applicativi finali.

Services Lifetimes

Il sistema permette l’aggiunta di 3 tipi di servizio:
Singleton

Il tipo di servizio più semplice che permette di aggiungere un’istanza condivisa globale per tutto il lifetime del ServiceProvider a cui viene aggiunta

Scoped

Il tipo scoped permette di aggiungere un’istanza condivisa per ogni scope del ServiceProvider a cui viene aggiunta

Transient

Il tipo transient creerà una nuova istanza del servizio ogni volta che esso verrà richiesto

Nota

Il concetto di Scope in ASP.NET Core viene inteso come suddivisione per richiesta ed è possibile ottenere il provider di riferimento per ogni richiesta dalla proprietà HttpContext.RequestServices

ASP.NET Core

Nel caso di ASP.NET Core le due fasi sopra citate vengono riassunte nel file Startup.cs e, più precisamente, nei due metodi ConfigureServices e Configure che, rispettivamente, permettono di aggiungere nuovi servizi all’applicativo e impostare l’ordine di esecuzione dei middleware che li utilizzeranno.

Gli standard per l’aggiunta di librerie suggeriscono l’utilizzo di metodi di estensione per aggiungere i servizi necessari all’uso di determinate funzionalità.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Example {
	public class Startup {
		public Startup(IConfiguration configuration) {
			Configuration = configuration;
		}

		public IConfiguration Configuration { get; }

		public void ConfigureServices(IServiceCollection services) {
			// Registrazione di un'istanza condivisa della classe implementata "DataProvider"
			services.AddSingleton<DataProvider>();
			// Aggiunta di un componente di una libreria esterna
			services.AddWebCoreAuthentication();
			services
			    .AddMvc() // Aggiunta di un componente
				.SetCompatibilityVersion(CompatibilityVersion.Version_2_2); // E conseguente configurazione dello stesso
		}

		public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
			Core.Init(new App(env.WebRootPath), false, app);
			if (env.IsDevelopment()) {
				app.UseDeveloperExceptionPage();
			} else {
				app.UseExceptionHandler("/Home/Error");
				app.UseHsts();
			}

			app.UseHttpsRedirection();
			app.UseStaticFiles();
			app.UseAuthentication();
			app.UseWebCore();

			// Il middleware MVC andrà ad utilizzare i servizi aggiunti da .AddMvc() precedentemente senza i quali non funzionerebbe
			app.UseMvc(routes => {
				routes.MapRoute(
					name: "default",
					template: "{controller=Home}/{action=Index}/{id?}");
			});
		}
	}
}

Nota

La configurazione dei servizi aggiunti, solitamente, avviene o tramite delegari passati come parametri (solitamente opzionali) oppure tramite il tipo di ritorno del metodo di estensione che può fungere da builder per l’aggiunta e la configurazione avanzata di funzionalità

In entrambi i metodi è molto importante l’ordine in cui vengono eseguiti i metodi di aggiunta in quanto nel primo caso le librerie tenteranno sempre di aggiungere i servizi con TryAdd[TIPO] in modo da permettere allo sviluppatore di registrare il servizio in questione prima di chiamare il metodo di aggiunta sovrascrivendo il servizio originale. Nel secondo caso, invece, cambia l’ordine di esecuzione dei middleware.

Un esempio di override può avvenire nel caso del servizio di autenticazione della libreria WebCore

services.AddSingleton<IAuthenticationService, CustomAuthenticationService>();
// Oppure services.AddScoped<IAuthenticationService>(new CustomAuthenticationService("some custom parameter"));
services.AddScoped<IAuthenticatorService, CustomAuthenticatorService>();
// Oppure services.AddScoped<IAuthenticatorService>(provider => new CustomAuthenticatorService("some custom parameter", provider.Resolve<SomeOtherService>()));
services.AddWebCoreAuthentication();

Importante

Se il metodo di estensione verrà chiamato prima dell’aggiunta dei servizi personalizzati verrà generato un errore per il tentativo di aggiunta di un servizio preesistente