Résolution des dépendances dans Xamarin.Forms

Cet article explique comment injecter une méthode Xamarin.Forms de résolution des dépendances afin que le conteneur d’injection de dépendances d’une application contrôle la création et la durée de vie des renderers, des effets et des implémentations DependencyService personnalisées.

Dans le contexte d’une Xamarin.Forms application qui utilise le modèle Model-View-ViewModel (MVVM), un conteneur d’injection de dépendances peut être utilisé pour inscrire et résoudre des modèles de vue, et pour inscrire des services et les injecter dans des modèles d’affichage. Lors de la création du modèle d’affichage, le conteneur injecte toutes les dépendances requises. Si ces dépendances n’ont pas été créées, le conteneur crée et résout d’abord les dépendances. Pour plus d’informations sur l’injection de dépendances, notamment des exemples d’injection de dépendances dans les modèles d’affichage, consultez Injection de dépendances.

Le contrôle de la création et de la durée de vie des types dans les projets de plateforme est traditionnellement effectué par Xamarin.Forms, qui utilise la Activator.CreateInstance méthode pour créer des instances de renderers, d’effets et DependencyService d’implémentations personnalisés. Malheureusement, cela limite le contrôle du développeur sur la création et la durée de vie de ces types, et la possibilité d’injecter des dépendances dans ces types. Ce comportement peut être modifié en injectant une méthode de résolution de dépendance dans Xamarin.Forms ce qui contrôle la façon dont les types seront créés , soit par le conteneur d’injection de dépendances de l’application, soit par Xamarin.Forms. Toutefois, notez qu’il n’est pas nécessaire d’injecter une méthode de résolution de dépendance dans Xamarin.Forms. Xamarin.Forms continuera à créer et à gérer la durée de vie des types dans les projets de plateforme si aucune méthode de résolution de dépendance n’est injectée.

Remarque

Bien que cet article se concentre sur l’injection d’une méthode Xamarin.Forms de résolution de dépendance dans ce qui résout les types inscrits à l’aide d’un conteneur d’injection de dépendances, il est également possible d’injecter une méthode de résolution de dépendance qui utilise des méthodes de fabrique pour résoudre les types inscrits.

Injection d’une méthode de résolution de dépendance

La DependencyResolver classe offre la possibilité d’injecter une méthode de résolution de dépendance dans Xamarin.Forms, à l’aide de la ResolveUsing méthode. Ensuite, lorsque vous Xamarin.Forms avez besoin d’une instance d’un type particulier, la méthode de résolution de dépendances a la possibilité de fournir l’instance. Si la méthode de résolution de dépendances retourne null un type demandé, Xamarin.Forms revient à tenter de créer l’instance de type elle-même à l’aide de la Activator.CreateInstance méthode.

L’exemple suivant montre comment définir la méthode de résolution des dépendances avec la ResolveUsing méthode :

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);
        ...
    }
    ...
}

Dans cet exemple, la méthode de résolution de dépendances est définie sur une expression lambda qui utilise le conteneur d’injection de dépendances Autofac pour résoudre tous les types inscrits auprès du conteneur. Sinon, null sera retourné, ce qui entraînera Xamarin.Forms une tentative de résolution du type.

Remarque

L’API utilisée par un conteneur d’injection de dépendances est spécifique au conteneur. Les exemples de code de cet article utilisent Autofac comme conteneur d’injection de dépendances, qui fournit les types et ContainerBuilder les IContainer types. D’autres conteneurs d’injection de dépendances peuvent également être utilisés, mais utilisent des API différentes que celles présentées ici.

Notez qu’il n’est pas nécessaire de définir la méthode de résolution des dépendances au démarrage de l’application. Elle peut être définie à tout moment. La seule contrainte est que Xamarin.Forms vous devez connaître la méthode de résolution des dépendances au moment où l’application tente de consommer des types stockés dans le conteneur d’injection de dépendances. Par conséquent, s’il existe des services dans le conteneur d’injection de dépendances requis par l’application au démarrage, la méthode de résolution des dépendances doit être définie au début du cycle de vie de l’application. De même, si le conteneur d’injection de dépendances gère la création et la durée de vie d’un particulier Effect, Xamarin.Forms vous devez connaître la méthode de résolution des dépendances avant de tenter de créer une vue qui utilise ce Effectfichier .

Avertissement

L’inscription et la résolution de types avec un conteneur d’injection de dépendances ont un coût de performances en raison de l’utilisation du conteneur pour la création de chaque type, en particulier si les dépendances sont reconstruites pour chaque navigation de page dans l’application. S’il existe de nombreuses dépendances ou des dépendances profondes, le coût de création peut augmenter de manière significative.

Inscription de types

Les types doivent être inscrits auprès du conteneur d’injection de dépendances avant de pouvoir les résoudre via la méthode de résolution des dépendances. L’exemple de code suivant montre les méthodes d’inscription exposées par l’exemple d’application dans la App classe, pour le conteneur 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();
    }
    ...
}

Lorsqu’une application utilise une méthode de résolution de dépendances pour résoudre les types à partir d’un conteneur, les inscriptions de types sont généralement effectuées à partir de projets de plateforme. Cela permet aux projets de plateforme d’inscrire des types pour les renderers, les effets et DependencyService les implémentations personnalisés.

Après l’inscription de type à partir d’un projet de plateforme, l’objet IContainer doit être généré, ce qui est effectué en appelant la BuildContainer méthode. Cette méthode appelle la méthode d’Autofac Build sur l’instance ContainerBuilder , qui génère un nouveau conteneur d’injection de dépendances qui contient les inscriptions qui ont été effectuées.

Dans les sections qui suivent, une Logger classe qui implémente l’interface ILogger est injectée dans des constructeurs de classe. La Logger classe implémente des fonctionnalités de journalisation simples à l’aide de la Debug.WriteLine méthode et sert à illustrer comment les services peuvent être injectés dans des renderers, des effets et DependencyService des implémentations personnalisés.

Inscription de renderers personnalisés

L’exemple d’application inclut une page qui lit des vidéos web, dont la source XAML est illustrée dans l’exemple suivant :

<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 vue est implémentée sur chaque plateforme par une VideoPlayerRenderer classe, qui fournit les fonctionnalités de lecture de la vidéo. Pour plus d’informations sur ces classes de renderer personnalisées, consultez Implémentation d’un lecteur vidéo.

Sur iOS et le plateforme Windows universelle (UWP), les VideoPlayerRenderer classes ont le constructeur suivant, ce qui nécessite un ILogger argument :

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

Sur toutes les plateformes, l’inscription de type avec le conteneur d’injection de dépendances est effectuée par la RegisterTypes méthode, qui est appelée avant le chargement de l’application par la plateforme avec la LoadApplication(new App()) méthode. L’exemple suivant montre la RegisterTypes méthode sur la plateforme iOS :

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

Dans cet exemple, le Logger type concret est inscrit via un mappage par rapport à son type d’interface, et le VideoPlayerRenderer type est inscrit directement sans mappage d’interface. Lorsque l’utilisateur accède à la page contenant l’affichage VideoPlayer , la méthode de résolution des dépendances est appelée pour résoudre le VideoPlayerRenderer type à partir du conteneur d’injection de dépendances, qui va également résoudre et injecter le Logger type dans le VideoPlayerRenderer constructeur.

Le VideoPlayerRenderer constructeur sur la plateforme Android est légèrement plus compliqué, car il nécessite un Context argument en plus de l’argument ILogger :

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

L’exemple suivant montre la RegisterTypes méthode sur la plateforme Android :

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

Dans cet exemple, la App.RegisterTypeWithParameters méthode inscrit auprès VideoPlayerRenderer du conteneur d’injection de dépendances. La méthode d’inscription garantit que l’instance MainActivity sera injectée en tant qu’argument Context et que le Logger type sera injecté en tant qu’argument ILogger .

Inscription d’effets

L’exemple d’application inclut une page qui utilise un effet de suivi tactile pour faire glisser BoxView des instances autour de la page. Il Effect est ajouté à l’utilisation BoxView du code suivant :

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

La TouchEffect classe est une RoutingEffect classe implémentée sur chaque plateforme par une TouchEffect classe qui est un PlatformEffect. La classe de plateforme TouchEffect fournit les fonctionnalités permettant de faire glisser le BoxView curseur autour de la page. Pour plus d’informations sur ces classes d’effet, consultez Appel d’événements à partir d’effets.

Sur toutes les plateformes, la TouchEffect classe a le constructeur suivant, ce qui nécessite un ILogger argument :

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

Sur toutes les plateformes, l’inscription de type avec le conteneur d’injection de dépendances est effectuée par la RegisterTypes méthode, qui est appelée avant le chargement de l’application par la plateforme avec la LoadApplication(new App()) méthode. L’exemple suivant montre la RegisterTypes méthode sur la plateforme Android :

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

Dans cet exemple, le Logger type concret est inscrit via un mappage par rapport à son type d’interface, et le TouchEffect type est inscrit directement sans mappage d’interface. Lorsque l’utilisateur accède à la page contenant une BoxView instance associée TouchEffect , la méthode de résolution des dépendances est appelée pour résoudre le type de plateforme TouchEffect à partir du conteneur d’injection de dépendances, qui va également résoudre et injecter le Logger type dans le TouchEffect constructeur.

Inscription d’implémentations DependencyService

L’exemple d’application inclut une page qui utilise DependencyService des implémentations sur chaque plateforme pour permettre à l’utilisateur de choisir une photo dans la bibliothèque d’images de l’appareil. L’interface IPhotoPicker définit les fonctionnalités implémentées par les DependencyService implémentations et est illustrée dans l’exemple suivant :

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

Dans chaque projet de plateforme, la PhotoPicker classe implémente l’interface à l’aide IPhotoPicker d’API de plateforme. Pour plus d’informations sur ces services de dépendance, consultez Sélection d’une photo à partir de la bibliothèque d’images.

Sur iOS et UWP, les PhotoPicker classes ont le constructeur suivant, ce qui nécessite un ILogger argument :

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

Sur toutes les plateformes, l’inscription de type avec le conteneur d’injection de dépendances est effectuée par la RegisterTypes méthode, qui est appelée avant le chargement de l’application par la plateforme avec la LoadApplication(new App()) méthode. L’exemple suivant montre la RegisterTypes méthode sur UWP :

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

Dans cet exemple, le Logger type concret est inscrit via un mappage par rapport à son type d’interface, et le PhotoPicker type est également inscrit via un mappage d’interface.

Le PhotoPicker constructeur sur la plateforme Android est légèrement plus compliqué, car il nécessite un Context argument en plus de l’argument ILogger :

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

L’exemple suivant montre la RegisterTypes méthode sur la plateforme Android :

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

Dans cet exemple, la App.RegisterTypeWithParameters méthode inscrit auprès PhotoPicker du conteneur d’injection de dépendances. La méthode d’inscription garantit que l’instance MainActivity sera injectée en tant qu’argument Context et que le Logger type sera injecté en tant qu’argument ILogger .

Lorsque l’utilisateur accède à la page de sélection de photos et choisit de sélectionner une photo, le OnSelectPhotoButtonClicked gestionnaire est exécuté :

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);
    }
    ...
}

Lorsque la DependencyService.Resolve<T> méthode est appelée, la méthode de résolution des dépendances est appelée pour résoudre le PhotoPicker type à partir du conteneur d’injection de dépendances, qui va également résoudre et injecter le Logger type dans le PhotoPicker constructeur.

Remarque

La Resolve<T> méthode doit être utilisée lors de la résolution d’un type à partir du conteneur d’injection de dépendances de l’application via le DependencyService.