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 Effect
fichier .
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
.