Condividi tramite


Inserimento delle dipendenze

Suggerimento

Questo contenuto è un estratto dell'eBook, Enterprise Application Patterns Using .NETMAUI, disponibile in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

In genere, un costruttore di classe viene richiamato quando si crea un'istanza di un oggetto e tutti i valori necessari all'oggetto vengono passati come argomenti al costruttore. Questo è un esempio di inserimento delle dipendenze noto come inserimento di costruttori. Le dipendenze necessarie per l'oggetto vengono inserite nel costruttore.

Specificando le dipendenze come tipi di interfaccia, l'inserimento delle dipendenze consente di separare i tipi concreti dal codice che dipende da questi tipi. In genere usa un contenitore che contiene un elenco di registrazioni e mapping tra interfacce e tipi astratti e i tipi concreti che implementano o estendono questi tipi.

Esistono anche altri tipi di inserimento delle dipendenze, ad esempio proprietà setter injection e metodo call injection, ma sono meno comunemente visti. Pertanto, questo capitolo si concentrerà esclusivamente sull'esecuzione dell'inserimento del costruttore con un contenitore di inserimento delle dipendenze.

Introduzione all'inserimento delle dipendenze

L'inserimento delle dipendenze è una versione specializzata del modello Inversion of Control (IoC), in cui il problema invertito è il processo di recupero della dipendenza richiesta. Con l'inserimento delle dipendenze, un'altra classe è responsabile dell'inserimento di dipendenze in un oggetto in fase di esecuzione. Nell'esempio di codice seguente viene illustrato come strutturare la classe ProfileViewModel quando si usa l'inserimento delle dipendenze:

private readonly ISettingsService _settingsService;
private readonly IAppEnvironmentService _appEnvironmentService;

public ProfileViewModel(
    IAppEnvironmentService appEnvironmentService,
    IDialogService dialogService, 
    INavigationService navigationService, 
    ISettingsService settingsService)
    : base(dialogService, navigationService, settingsService)
{
    _appEnvironmentService = appEnvironmentService;
    _settingsService = settingsService;

    // Omitted for brevity
}

Il costruttore ProfileViewModel riceve più istanze di oggetti di interfaccia come argomenti inseriti da un'altra classe. L'unica dipendenza nella classe ProfileViewModel è sui tipi di interfaccia. Pertanto, la classe ProfileViewModel non ha alcuna conoscenza della classe responsabile della creazione di istanze degli oggetti interfaccia. La classe responsabile della creazione di un'istanza degli oggetti di interfaccia e dell'inserimento nella classe ProfileViewModel è nota come contenitore di inserimento delle dipendenze.

I contenitori di inserimento delle dipendenze riducono l'accoppiamento tra oggetti fornendo una funzionalità per creare istanze di classe e gestirle in base alla configurazione del contenitore. Durante la creazione dell'oggetto, il contenitore inserisce tutte le dipendenze richieste dall'oggetto. Se tali dipendenze non sono ancora state create, il contenitore crea e risolve prima le relative dipendenze.

L'uso di un contenitore di inserimento delle dipendenze offre diversi vantaggi:

  • Un contenitore rimuove la necessità di una classe per individuare le relative dipendenze e gestirle.
  • Un contenitore consente il mapping delle dipendenze implementate senza influire sulla classe.
  • Un contenitore facilita la testabilità consentendo la simulazione delle dipendenze.
  • Un contenitore aumenta la manutenibilità consentendo di aggiungere facilmente nuove classi all'app.

Nel contesto di un'app .NET MAUI che usa MVVM, in genere verrà usato un contenitore di inserimento delle dipendenze per la registrazione e la risoluzione delle visualizzazioni, la registrazione e la risoluzione dei modelli di visualizzazione e per la registrazione dei servizi e l'inserimento dei servizi nei modelli di visualizzazione.

In .NET sono disponibili molti contenitori di inserimento delle dipendenze; l'app multipiattaforma eShopOnContainers usa Microsoft.Extensions.DependencyInjection per gestire la creazione di istanze di visualizzazioni, modelli di visualizzazione e classi di servizio nell'app. Microsoft.Extensions.DependencyInjection facilita la creazione di app ad accoppiamento libero e fornisce tutte le funzionalità comunemente disponibili nei contenitori di inserimento delle dipendenze, inclusi i metodi per registrare mapping dei tipi e istanze di oggetti, risolvere gli oggetti, gestire la durata degli oggetti e inserire oggetti dipendenti nei costruttori di oggetti risolti. Per ulteriori informazioni su Microsoft.Extensions.DependencyInjection, consultare inserimento delle dipendenze in .NET.

In .NET MAUI la classe MauiProgram chiamerà nel metodo CreateMauiApp per creare un oggetto MauiAppBuilder. L'oggetto MauiAppBuilder ha una proprietà Services di tipo IServiceCollection, che consente di registrare i componenti, ad esempio visualizzazioni, modelli di visualizzazione e servizi per l'inserimento delle dipendenze. Tutti i componenti registrati con la proprietà Services verranno forniti al contenitore di inserimento delle dipendenze quando viene chiamato il metodo MauiAppBuilder.Build.

In fase di esecuzione, il contenitore deve conoscere l'implementazione dei servizi richiesta per crearne un'istanza per gli oggetti richiesti. Nell'app multipiattaforma eShopOnContainers, le interfacce IAppEnvironmentService, IDialogService , INavigationService e ISettingsService devono essere risolte prima di poter creare un'istanza di un oggetto ProfileViewModel. Ciò comporta l'esecuzione delle azioni seguenti per il contenitore:

  • Decidere come creare un'istanza di un oggetto che implementa l'interfaccia. Questa operazione è nota come registrazione.
  • Creazione di un'istanza dell'oggetto che implementa l'interfaccia richiesta e l'oggetto ProfileViewModel. Questa operazione è nota come risoluzione.

Alla fine, l'app terminerà l'uso dell'oggetto ProfileViewModel e diventerà disponibile per l'operazione di Garbage Collection. A questo punto, il Garbage Collector deve eliminare eventuali implementazioni di interfacce di breve durata se altre classi non condividono la stessa istanza.

Registrazione

Prima che le dipendenze possano essere inserite in un oggetto, i tipi delle dipendenze devono essere prima registrati con il contenitore. La registrazione di un tipo comporta il passaggio del contenitore di un'interfaccia e un tipo concreto che implementa l'interfaccia.

Esistono due modi per registrare i tipi e gli oggetti nel contenitore tramite il codice:

  • Registrare un tipo o un mapping con il contenitore. Questa operazione è nota come registrazione temporanea. Se necessario, il contenitore compilerà un'istanza del tipo specificato.
  • Registrare un oggetto esistente nel contenitore come singleton. Se necessario, il contenitore restituirà un riferimento all'oggetto esistente.

Nota

I contenitori di inserimento delle dipendenze non sono sempre adatti. L'inserimento delle dipendenze introduce complessità e requisiti aggiuntivi che potrebbero non essere appropriati o utili per le piccole app. Se una classe non ha dipendenze o non è una dipendenza per altri tipi, potrebbe non essere opportuno inserirla nel contenitore. Inoltre, se una classe ha un singolo set di dipendenze che sono integrali al tipo e non cambierà mai, potrebbe non essere opportuno inserirla nel contenitore.

La registrazione dei tipi che richiedono l'inserimento delle dipendenze deve essere eseguita in un singolo metodo in un'app. Questo metodo deve essere richiamato all'inizio del ciclo di vita dell'app per assicurarsi che sia a conoscenza delle dipendenze tra le relative classi. L'app multipiattaforma eShopOnContainers esegue questo metodo MauiProgram.CreateMauiApp. Dopo aver registrato tutti i servizi, è necessario chiamare il metodo CreateMauiApp per creare il MauiProgram e popolare il contenitore di inserimento delle dipendenze con tutti i servizi registrati:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            // Omitted for brevity            
            .RegisterAppServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();
}

Il metodo MauiApp.CreateBuilder crea un oggetto MauiAppBuilder che è possibile usare per registrare le dipendenze. Molte dipendenze nell'app multipiattaforma eShopOnContainers devono essere registrate, quindi i metodi di estensione RegisterAppServices, RegisterViewModels e RegisterViews sono stati creati per fornire un flusso di lavoro di registrazione organizzato e gestibile. Nel codice seguente viene illustrato il metodo RegisterViewModels.

public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();

    mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();

    return mauiAppBuilder;
}

Questo metodo riceve un'istanza di MauiAppBuilder ed è possibile usare la proprietà Services per registrare i modelli di visualizzazione. A seconda delle esigenze dell'applicazione, potrebbe essere necessario aggiungere servizi con durate diverse. La tabella seguente fornisce informazioni su quando è possibile scegliere queste diverse durate di registrazione:

metodo Descrizione
AddSingleton<T> Verrà creata una singola istanza dell'oggetto che rimarrà per la durata dell'applicazione.
AddTransient<T> Crea una nuova istanza dell'oggetto quando richiesto durante la risoluzione. Gli oggetti temporanei non hanno una durata predefinita, ma in genere seguiranno la durata dell'host.

Nota

I modelli di visualizzazione non ereditano da un'interfaccia, quindi hanno bisogno solo del tipo concreto fornito ai metodi AddSingleton<T> e AddTransient<T>.

CatalogViewModel viene usato vicino alla radice dell'applicazione e deve essere sempre disponibile, quindi la registrazione con AddSingleton<T> è utile. Altri modelli di visualizzazione, ad esempio CheckoutViewModel e OrderDetailViewModel vengono spostati in modo situazione a o vengono usati in un secondo momento nell'applicazione. Si supponga di avere un componente che potrebbe non essere sempre usato. In tal caso, se si tratta di memoria o a elevato utilizzo di calcolo o richiede dati just-in-time, può essere un candidato migliore per AddTransient<T> registrazione.

Un altro modo comune per aggiungere servizi consiste nell'usare i metodi AddSingleton<TService, TImplementation> e AddTransient<TService, TImplementation>. Questi metodi accettano due tipi di input: la definizione dell'interfaccia e l'implementazione concreta. Questo tipo di registrazione è ideale per i casi in cui si implementano servizi basati su interfacce. Nell'esempio di codice seguente si registra l'interfaccia ISettingsService usando l'implementazione SettingsService:

public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
    // Omitted for brevity...
}

Dopo aver registrato tutti i servizi, è necessario chiamare il metodo MauiAppBuilder.Build per creare il MauiApp e popolare il contenitore di inserimento delle dipendenze con tutti i servizi registrati.

Importante

Dopo aver chiamato il metodo Build, i servizi registrati con il contenitore di inserimento delle dipendenze non saranno modificabili e non potranno più essere aggiornati o modificati.

Risoluzione

Dopo la registrazione di un tipo, può essere risolto o inserito come dipendenza. Quando viene risolto un tipo e il contenitore deve creare una nuova istanza, inserisce eventuali dipendenze nell'istanza.

In genere, quando viene risolto un tipo, si verifica una delle tre operazioni seguenti:

  1. Se il tipo non è stato registrato, il contenitore genera un'eccezione.
  2. Se il tipo è stato registrato come singleton, il contenitore restituisce l'istanza singleton. Se è la prima volta che viene chiamato il tipo, il contenitore lo crea se necessario e mantiene un riferimento a esso.
  3. Se il tipo è stato registrato come temporaneo, il contenitore restituisce una nuova istanza e non ne mantiene un riferimento.

.NET MAUI offre diversi modi per risolvere i componenti registrati in base alle esigenze. Il modo più diretto per ottenere l'accesso al contenitore di inserimento delle dipendenze è da un Element usando il Handler.MauiContext.Services. Di seguito è illustrato un esempio di questa situazione:

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

Questo può essere utile se è necessario risolvere un servizio dall'interno di un Element o dall'esterno del costruttore del Element.

Attenzione

È possibile che la proprietà Handler del Element possa essere null, quindi tenere presente che potrebbe essere necessario gestire tali situazioni. Per ulteriori informazioni, consultare Ciclo di vita del gestore nel Centro documentazione Microsoft.

Se si usa il controllo Shell per .NET MAUI, verrà chiamato in modo implicito nel contenitore di inserimento delle dipendenze per creare gli oggetti durante la navigazione. Quando si configura il controllo Shell, il metodo Routing.RegisterRoute collega un percorso a un View come illustrato nell'esempio seguente:

Routing.RegisterRoute("Filter", typeof(FiltersView));

Durante la navigazione Shell, cercherà le registrazioni di FiltersView e, se presenti, creerà tale visualizzazione e inserirà eventuali dipendenze nel costruttore. Come illustrato nell'esempio di codice seguente, il CatalogViewModel verrà inserito nel FiltersView:

namespace eShopOnContainers.Views;

public partial class FiltersView : ContentPage
{
    public FiltersView(CatalogViewModel viewModel)
    {
        BindingContext = viewModel;

        InitializeComponent();
    }
}

Suggerimento

Il contenitore di inserimento delle dipendenze è ideale per la creazione di istanze del modello di visualizzazione. Se un modello di visualizzazione presenta dipendenze, gestirà la creazione e l'inserimento di tutti i servizi necessari. Assicurarsi di registrare i modelli di visualizzazione e le eventuali dipendenze che potrebbero avere con il metodo CreateMauiApp nella classe MauiProgram.

Riepilogo

L'inserimento delle dipendenze consente di separare i tipi concreti dal codice che dipende da questi tipi. In genere usa un contenitore che contiene un elenco di registrazioni e mapping tra interfacce e tipi astratti e i tipi concreti che implementano o estendono questi tipi.

Microsoft.Extensions.DependencyInjection facilita la creazione di app ad accoppiamento libero e fornisce tutte le funzionalità comunemente disponibili nei contenitori di inserimento delle dipendenze, inclusi i metodi per registrare mapping dei tipi e istanze di oggetti, risolvere gli oggetti, gestire la durata degli oggetti e inserire oggetti dipendenti nei costruttori di oggetti risolti.