Risoluzione delle dipendenze in Xamarin.Forms

Questo articolo illustra come inserire un metodo di risoluzione delle dipendenze in Xamarin.Forms in modo che il contenitore di inserimento delle dipendenze di un'applicazione abbia il controllo sulla creazione e sulla durata di renderer, effetti e implementazioni di DependencyService personalizzate.

Nel contesto di un'applicazione Xamarin.Forms che usa il modello Model-View-ViewModel (MVVM), è possibile usare un contenitore di inserimento delle dipendenze per registrare e risolvere i modelli di visualizzazione e per registrare i servizi e inserirli nei modelli di visualizzazione. Durante la creazione del modello di visualizzazione, il contenitore inserisce tutte le dipendenze necessarie. Se tali dipendenze non sono state create, il contenitore crea e risolve prima le dipendenze. Per altre informazioni sull'inserimento delle dipendenze, inclusi esempi di inserimento di dipendenze nei modelli di visualizzazione, vedere Inserimento delle dipendenze.

Il controllo sulla creazione e sulla durata dei tipi nei progetti di piattaforma viene tradizionalmente eseguito da Xamarin.Forms, che usa il Activator.CreateInstance metodo per creare istanze di renderer personalizzati, effetti e DependencyService implementazioni. Sfortunatamente, questo limita il controllo degli sviluppatori sulla creazione e sulla durata di questi tipi e la possibilità di inserire le dipendenze in tali tipi. Questo comportamento può essere modificato inserendo un metodo di risoluzione delle dipendenze in Xamarin.Forms che controlla la modalità di creazione dei tipi, dal contenitore di inserimento delle dipendenze dell'applicazione o da Xamarin.Forms. Si noti tuttavia che non è necessario inserire un metodo di risoluzione delle dipendenze in Xamarin.Forms. Xamarin.Forms continuerà a creare e gestire la durata dei tipi nei progetti di piattaforma se non viene inserito un metodo di risoluzione delle dipendenze.

Nota

Anche se questo articolo è incentrato sull'inserimento di un metodo di risoluzione delle dipendenze in Xamarin.Forms che risolve i tipi registrati usando un contenitore di inserimento delle dipendenze, è anche possibile inserire un metodo di risoluzione delle dipendenze che usa i metodi factory per risolvere i tipi registrati.

Inserimento di un metodo di risoluzione delle dipendenze

La DependencyResolver classe consente di inserire un metodo di risoluzione delle dipendenze in Xamarin.Formsusando il ResolveUsing metodo . Quindi, quando Xamarin.Forms è necessaria un'istanza di un particolare tipo, al metodo di risoluzione delle dipendenze viene data la possibilità di fornire l'istanza. Se il metodo di risoluzione delle dipendenze restituisce null per un tipo richiesto, Xamarin.Forms esegue il fallback al tentativo di creare l'istanza del tipo stesso usando il Activator.CreateInstance metodo .

Nell'esempio seguente viene illustrato come impostare il metodo di risoluzione delle dipendenze con il ResolveUsing metodo :

using Autofac;
using Xamarin.Forms.Internals;
...

public partial class App : Application
{
    // IContainer and ContainerBuilder are provided by Autofac
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();

    public App()
    {
        ...
        DependencyResolver.ResolveUsing(type => container.IsRegistered(type) ? container.Resolve(type) : null);
        ...
    }
    ...
}

In questo esempio il metodo di risoluzione delle dipendenze viene impostato su un'espressione lambda che usa il contenitore di inserimento delle dipendenze Autofac per risolvere tutti i tipi registrati con il contenitore. In caso contrario, null verrà restituito , il che comporterà il Xamarin.Forms tentativo di risolvere il tipo.

Nota

L'API usata da un contenitore di inserimento delle dipendenze è specifica per il contenitore. Gli esempi di codice in questo articolo usano Autofac come contenitore di inserimento delle dipendenze, che fornisce i IContainer tipi e ContainerBuilder . I contenitori alternativi di inserimento delle dipendenze potrebbero essere usati allo stesso modo, ma userebbero API diverse rispetto a quelle presentate qui.

Si noti che non è necessario impostare il metodo di risoluzione delle dipendenze durante l'avvio dell'applicazione. Può essere impostato in qualsiasi momento. L'unico vincolo è che Xamarin.Forms deve conoscere il metodo di risoluzione delle dipendenze nel momento in cui l'applicazione tenta di utilizzare i tipi archiviati nel contenitore di inserimento delle dipendenze. Pertanto, se nel contenitore di inserimento delle dipendenze sono presenti servizi che l'applicazione richiederà durante l'avvio, il metodo di risoluzione delle dipendenze dovrà essere impostato all'inizio del ciclo di vita dell'applicazione. Analogamente, se il contenitore di inserimento delle dipendenze gestisce la creazione e la durata di un particolare Effect, Xamarin.Forms sarà necessario conoscere il metodo di risoluzione delle dipendenze prima di tentare di creare una vista che usa tale Effect.

Avviso

La registrazione e la risoluzione dei tipi con un contenitore di inserimento delle dipendenze comporta un costo delle prestazioni a causa dell'uso della reflection del contenitore per la creazione di ogni tipo, soprattutto se le dipendenze vengono ricostruite per ogni spostamento di pagina nell'applicazione. Se le dipendenze presenti sono numerose o complete, il costo della creazione può aumentare in modo significativo.

Registrazione dei tipi

I tipi devono essere registrati con il contenitore di inserimento delle dipendenze prima di poterli risolvere tramite il metodo di risoluzione delle dipendenze. L'esempio di codice seguente illustra i metodi di registrazione esposti dall'applicazione di esempio nella App classe per il contenitore Autofac:

using Autofac;
using Autofac.Core;
...

public partial class App : Application
{
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();
    ...

    public static void RegisterType<T>() where T : class
    {
        builder.RegisterType<T>();
    }

    public static void RegisterType<TInterface, T>() where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>().As<TInterface>();
    }

    public static void RegisterTypeWithParameters<T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where T : class
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        });
    }

    public static void RegisterTypeWithParameters<TInterface, T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        }).As<TInterface>();
    }

    public static void BuildContainer()
    {
        container = builder.Build();
    }
    ...
}

Quando un'applicazione usa un metodo di risoluzione delle dipendenze per risolvere i tipi da un contenitore, le registrazioni dei tipi vengono in genere eseguite dai progetti della piattaforma. In questo modo, i progetti della piattaforma possono registrare tipi per renderer, effetti e DependencyService implementazioni personalizzati.

Dopo la registrazione del tipo da un progetto di piattaforma, l'oggetto IContainer deve essere compilato, che viene eseguito chiamando il BuildContainer metodo . Questo metodo richiama il metodo autofac sull'istanza BuildContainerBuilder di , che compila un nuovo contenitore di inserimento delle dipendenze che contiene le registrazioni effettuate.

Nelle sezioni seguenti una Logger classe che implementa l'interfaccia ILogger viene inserita nei costruttori di classi. La Logger classe implementa funzionalità di registrazione semplici usando il Debug.WriteLine metodo e viene usata per illustrare come i servizi possono essere inseriti in renderer, effetti e DependencyService implementazioni personalizzati.

Registrazione di renderer personalizzati

L'applicazione di esempio include una pagina che riproduce video Web, la cui origine XAML è illustrata nell'esempio seguente:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:video="clr-namespace:FormsVideoLibrary"
             ...>
    <video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>

La VideoPlayer visualizzazione viene implementata in ogni piattaforma da una VideoPlayerRenderer classe , che fornisce la funzionalità per la riproduzione del video. Per altre informazioni su queste classi di renderer personalizzate, vedere Implementazione di un lettore video.

In iOS e nella piattaforma UWP (Universal Windows Platform) (UWP), le VideoPlayerRenderer classi hanno il costruttore seguente, che richiede un ILogger argomento:

public VideoPlayerRenderer(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

In tutte le piattaforme, digitare la registrazione con il contenitore di inserimento delle dipendenze viene eseguita dal RegisterTypes metodo , che viene richiamato prima della piattaforma che carica l'applicazione con il LoadApplication(new App()) metodo . L'esempio seguente illustra il RegisterTypes metodo nella piattaforma iOS:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
    App.BuildContainer();
}

In questo esempio, il Logger tipo concreto viene registrato tramite un mapping rispetto al tipo di interfaccia e il VideoPlayerRenderer tipo viene registrato direttamente senza mapping dell'interfaccia. Quando l'utente passa alla pagina contenente la VideoPlayer visualizzazione, il metodo di risoluzione delle dipendenze verrà richiamato per risolvere il VideoPlayerRenderer tipo dal contenitore di inserimento delle dipendenze, che risolverà e inserisce il Logger tipo nel VideoPlayerRenderer costruttore.

Il VideoPlayerRenderer costruttore sulla piattaforma Android è leggermente più complesso perché richiede un Context argomento oltre all'argomento ILogger :

public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

L'esempio seguente illustra il RegisterTypes metodo sulla piattaforma Android:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

In questo esempio il App.RegisterTypeWithParameters metodo registra con VideoPlayerRenderer il contenitore di inserimento delle dipendenze. Il metodo di registrazione garantisce che l'istanza MainActivity venga inserita come Context argomento e che il Logger tipo venga inserito come ILogger argomento.

Registrazione degli effetti

L'applicazione di esempio include una pagina che usa un effetto di rilevamento tocco per trascinare BoxView le istanze nella pagina. L'oggetto Effect viene aggiunto all'oggetto BoxView usando il codice seguente:

var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);

La TouchEffect classe è un RoutingEffect oggetto implementato in ogni piattaforma da una TouchEffect classe che è un PlatformEffectoggetto . La classe platform TouchEffect fornisce la funzionalità per il trascinamento della BoxView pagina. Per altre informazioni su queste classi di effetti, vedere Richiamo di eventi dagli effetti.

In tutte le piattaforme, la TouchEffect classe ha il costruttore seguente, che richiede un ILogger argomento:

public TouchEffect(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

In tutte le piattaforme, digitare la registrazione con il contenitore di inserimento delle dipendenze viene eseguita dal RegisterTypes metodo , che viene richiamato prima della piattaforma che carica l'applicazione con il LoadApplication(new App()) metodo . L'esempio seguente illustra il RegisterTypes metodo sulla piattaforma Android:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<TouchTracking.Droid.TouchEffect>();
    App.BuildContainer();
}

In questo esempio, il Logger tipo concreto viene registrato tramite un mapping rispetto al tipo di interfaccia e il TouchEffect tipo viene registrato direttamente senza mapping dell'interfaccia. Quando l'utente passa alla pagina contenente un'istanza BoxViewTouchEffect associata, il metodo di risoluzione delle dipendenze verrà richiamato per risolvere il tipo di piattaforma TouchEffect dal contenitore di inserimento delle dipendenze, che risolverà e inserirà il Logger tipo nel TouchEffect costruttore.

Registrazione delle implementazioni di DependencyService

L'applicazione di esempio include una pagina che usa DependencyService implementazioni in ogni piattaforma per consentire all'utente di selezionare una foto dalla raccolta immagini del dispositivo. L'interfaccia IPhotoPicker definisce la funzionalità implementata dalle DependencyService implementazioni ed è illustrata nell'esempio seguente:

public interface IPhotoPicker
{
    Task<Stream> GetImageStreamAsync();
}

In ogni progetto di piattaforma, la PhotoPicker classe implementa l'interfaccia IPhotoPicker usando le API della piattaforma. Per altre informazioni su questi servizi di dipendenza, vedere Selezione di una foto dalla raccolta immagini.

In iOS e UWP le PhotoPicker classi hanno il costruttore seguente, che richiede un ILogger argomento:

public PhotoPicker(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

In tutte le piattaforme, digitare la registrazione con il contenitore di inserimento delle dipendenze viene eseguita dal RegisterTypes metodo , che viene richiamato prima della piattaforma che carica l'applicazione con il LoadApplication(new App()) metodo . L'esempio seguente mostra il RegisterTypes metodo nella piattaforma UWP:

void RegisterTypes()
{
    DIContainerDemo.App.RegisterType<ILogger, Logger>();
    DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
    DIContainerDemo.App.BuildContainer();
}

In questo esempio, il Logger tipo concreto viene registrato tramite un mapping rispetto al tipo di interfaccia e il PhotoPicker tipo viene registrato anche tramite un mapping dell'interfaccia.

Il PhotoPicker costruttore sulla piattaforma Android è leggermente più complesso perché richiede un Context argomento oltre all'argomento ILogger :

public PhotoPicker(Context context, ILogger logger)
{
    _context = context ?? throw new ArgumentNullException(nameof(context));
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

L'esempio seguente illustra il RegisterTypes metodo sulla piattaforma Android:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

In questo esempio il App.RegisterTypeWithParameters metodo registra con PhotoPicker il contenitore di inserimento delle dipendenze. Il metodo di registrazione garantisce che l'istanza MainActivity venga inserita come Context argomento e che il Logger tipo venga inserito come ILogger argomento.

Quando l'utente passa alla pagina di selezione delle foto e sceglie di selezionare una foto, viene eseguito il OnSelectPhotoButtonClicked gestore:

async void OnSelectPhotoButtonClicked(object sender, EventArgs e)
{
    ...
    var photoPickerService = DependencyService.Resolve<IPhotoPicker>();
    var stream = await photoPickerService.GetImageStreamAsync();
    if (stream != null)
    {
        image.Source = ImageSource.FromStream(() => stream);
    }
    ...
}

Quando viene richiamato il DependencyService.Resolve<T> metodo di risoluzione delle dipendenze, verrà richiamato il metodo di risoluzione delle dipendenze per risolvere il PhotoPicker tipo dal contenitore di inserimento delle dipendenze, che risolve e inserisce anche il Logger tipo nel PhotoPicker costruttore.

Nota

Il Resolve<T> metodo deve essere usato per risolvere un tipo dal contenitore di inserimento delle dipendenze dell'applicazione tramite .DependencyService