Abhängigkeitsauflösung in Xamarin.Forms
In diesem Artikel wird erläutert, wie Eine Abhängigkeitsauflösungsmethode Xamarin.Forms eingefügt wird, sodass der Container zum Einfügen von Abhängigkeiten einer Anwendung kontrolle über die Erstellung und Lebensdauer von benutzerdefinierten Renderern, Effekten und DependencyService-Implementierungen verfügt.
Im Kontext einer Xamarin.Forms Anwendung, die das Model-View-ViewModel (MVVM)-Muster verwendet, kann ein Abhängigkeitseinfügungscontainer zum Registrieren und Auflösen von Ansichtsmodellen und zum Registrieren von Diensten und zum Einfügen in Ansichtsmodelle verwendet werden. Während der Erstellung des Ansichtsmodells fügt der Container alle erforderlichen Abhängigkeiten ein. Wenn diese Abhängigkeiten nicht erstellt wurden, erstellt und löst der Container zuerst die Abhängigkeiten. Weitere Informationen zum Einfügen von Abhängigkeiten, einschließlich Beispiele für das Einfügen von Abhängigkeiten in Ansichtsmodelle, finden Sie unter Dependency Injection.
Die Kontrolle über die Erstellung und Lebensdauer von Typen in Plattformprojekten wird traditionell durchgeführt Xamarin.Forms, die die Activator.CreateInstance
Methode zum Erstellen von Instanzen von benutzerdefinierten Renderern, Effekten und DependencyService
Implementierungen verwendet. Leider beschränkt dies die Kontrolle des Entwicklers über die Erstellung und Lebensdauer dieser Typen und die Fähigkeit, Abhängigkeiten in sie einzufügen. Dieses Verhalten kann geändert werden, indem eine Abhängigkeitsauflösungsmethode eingefügt Xamarin.Forms wird, die steuert, wie Typen erstellt werden , entweder durch den Abhängigkeitseinfügungscontainer der Anwendung oder durch Xamarin.Forms. Beachten Sie jedoch, dass es keine Anforderung gibt, eine Abhängigkeitsauflösungsmethode in Xamarin.Formsdie Datei einzufügen. Xamarin.Forms erstellt und verwaltet weiterhin die Lebensdauer von Typen in Plattformprojekten, wenn keine Abhängigkeitsauflösungsmethode eingefügt wird.
Hinweis
Dieser Artikel konzentriert sich zwar auf das Einfügen einer Abhängigkeitsauflösungsmethode, die Xamarin.Forms registrierte Typen mithilfe eines Abhängigkeitseinfügungscontainers auflösen kann, aber es ist auch möglich, eine Abhängigkeitsauflösungsmethode einzufügen, die Factorymethoden verwendet, um registrierte Typen aufzulösen.
Einfügen einer Abhängigkeitsauflösungsmethode
Die DependencyResolver
Klasse bietet die Möglichkeit, eine Abhängigkeitsauflösungsmethode Xamarin.Formsmithilfe der ResolveUsing
Methode einzufügen. Wenn Xamarin.Forms dann eine Instanz eines bestimmten Typs benötigt wird, erhält die Abhängigkeitsauflösungsmethode die Möglichkeit, die Instanz bereitzustellen. Wenn die Abhängigkeitsauflösungsmethode für einen angeforderten Typ zurückgegeben wird null
, greift sie auf den Versuch zurück, Xamarin.Forms die Typinstanz selbst mithilfe der Activator.CreateInstance
Methode zu erstellen.
Das folgende Beispiel zeigt, wie die Abhängigkeitsauflösungsmethode mit der ResolveUsing
Methode festgelegt wird:
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 diesem Beispiel wird die Abhängigkeitsauflösungsmethode auf einen Lambda-Ausdruck festgelegt, der den Container für die Autofac-Abhängigkeitseinfügung verwendet, um alle Typen aufzulösen, die beim Container registriert wurden. Andernfalls wird der Typ zurückgegeben, was dazu führtXamarin.Forms, null
dass versucht wird, den Typ aufzulösen.
Hinweis
Die api, die von einem Abhängigkeitseinfügungscontainer verwendet wird, ist spezifisch für den Container. Die Codebeispiele in diesem Artikel verwenden Autofac als Abhängigkeitseinfügungscontainer, der die IContainer
Und-Typen ContainerBuilder
bereitstellt. Alternative Abhängigkeitseinfügungscontainer könnten ebenfalls verwendet werden, würden aber andere APIs verwenden, als hier dargestellt werden.
Beachten Sie, dass beim Starten der Anwendung keine Abhängigkeitsauflösungsmethode festgelegt werden muss. Sie kann jederzeit festgelegt werden. Die einzige Einschränkung besteht darin, dass Xamarin.Forms sie die Abhängigkeitsauflösungsmethode nach dem Zeitpunkt kennen muss, zu dem die Anwendung versucht, typen zu nutzen, die im Container zum Einfügen von Abhängigkeiten gespeichert sind. Wenn also Dienste im Container zum Einfügen von Abhängigkeiten vorhanden sind, die die Anwendung während des Starts benötigt, muss die Abhängigkeitsauflösungsmethode frühzeitig im Lebenszyklus der Anwendung festgelegt werden. Wenn der Container zum Einfügen von Abhängigkeiten die Erstellung und Lebensdauer eines bestimmten Effect
Containers verwaltet, Xamarin.Forms muss die Methode für die Abhängigkeitsauflösung ebenfalls bekannt sein, bevor versucht wird, eine Ansicht zu erstellen, die diese Effect
verwendet.
Warnung
Das Registrieren und Auflösen von Typen mit einem Abhängigkeitseinfügungscontainer hat aufgrund der Verwendung der Reflexion des Containers zum Erstellen jedes Typs eine Leistungskosten, insbesondere, wenn Abhängigkeiten für jede Seitennavigation in der Anwendung rekonstruiert werden. Wenn zahlreiche oder tiefe Abhängigkeiten vorhanden sind, können die Kosten für die Erstellung erheblich zunehmen.
Registrieren von Typen
Typen müssen beim Container zum Einfügen von Abhängigkeiten registriert werden, bevor sie über die Abhängigkeitsauflösungsmethode aufgelöst werden können. Das folgende Codebeispiel zeigt die Registrierungsmethoden, die die Beispielanwendung in der App
Klasse für den Autofac-Container verfügbar macht:
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();
}
...
}
Wenn eine Anwendung eine Abhängigkeitsauflösungsmethode verwendet, um Typen aus einem Container aufzulösen, werden Typenregistrierungen in der Regel aus Plattformprojekten ausgeführt. Auf diese Weise können Plattformprojekte Typen für benutzerdefinierte Renderer, Effekte und DependencyService
Implementierungen registrieren.
Nach der Typregistrierung aus einem Plattformprojekt muss das IContainer
Objekt erstellt werden, das durch Aufrufen der BuildContainer
Methode erreicht wird. Diese Methode ruft die Autofac-Methode Build
für die ContainerBuilder
Instanz auf, die einen neuen Container zum Einfügen von Abhängigkeiten erstellt, der die registrierungen enthält, die vorgenommen wurden.
In den folgenden Abschnitten wird eine Logger
Klasse, die die ILogger
Schnittstelle implementiert, in Klassenkonstruktoren eingefügt. Die Logger
Klasse implementiert einfache Protokollierungsfunktionen mithilfe der Debug.WriteLine
Methode und wird verwendet, um zu veranschaulichen, wie Dienste in benutzerdefinierte Renderer, Effekte und DependencyService
Implementierungen eingefügt werden können.
Registrieren von benutzerdefinierten Renderern
Die Beispielanwendung enthält eine Seite, die Webvideos wiedergibt, deren XAML-Quelle im folgenden Beispiel gezeigt wird:
<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>
Die VideoPlayer
Ansicht wird auf jeder Plattform durch eine VideoPlayerRenderer
Klasse implementiert, die die Funktionalität für die Wiedergabe des Videos bereitstellt. Weitere Informationen zu diesen benutzerdefinierten Rendererklassen finden Sie unter Implementieren eines Videoplayers.
Unter iOS und dem Universelle Windows-Plattform (UWP) weisen die VideoPlayerRenderer
Klassen den folgenden Konstruktor auf, der ein ILogger
Argument erfordert:
public VideoPlayerRenderer(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Auf allen Plattformen erfolgt die Typregistrierung mit dem Abhängigkeitseinfügungscontainer durch die RegisterTypes
Methode, die vor dem Laden der Anwendung mit der LoadApplication(new App())
Methode aufgerufen wird. Das folgende Beispiel zeigt die RegisterTypes
Methode auf der iOS-Plattform:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
App.BuildContainer();
}
In diesem Beispiel wird der Logger
konkrete Typ über eine Zuordnung mit seinem Schnittstellentyp registriert, und der VideoPlayerRenderer
Typ wird direkt ohne Schnittstellenzuordnung registriert. Wenn der Benutzer zu der Seite navigiert, die die VideoPlayer
Ansicht enthält, wird die Abhängigkeitsauflösungsmethode aufgerufen, um den VideoPlayerRenderer
Typ aus dem Container zum Einfügen von Abhängigkeiten aufzulösen und in den VideoPlayerRenderer
Konstruktor einzufügenLogger
.
Der VideoPlayerRenderer
Konstruktor auf der Android-Plattform ist etwas komplizierter, da es zusätzlich zum ILogger
Argument ein Context
Argument erfordert:
public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Das folgende Beispiel zeigt die RegisterTypes
Methode auf der Android-Plattform:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
In diesem Beispiel registriert die App.RegisterTypeWithParameters
Methode den VideoPlayerRenderer
Container zum Einfügen von Abhängigkeiten. Die Registrierungsmethode stellt sicher, dass die MainActivity
Instanz als Context
Argument eingefügt wird und dass der Logger
Typ als ILogger
Argument eingefügt wird.
Registrieren von Effekten
Die Beispielanwendung enthält eine Seite, die einen Touchverfolgungseffekt verwendet, um Instanzen um die Seite zu ziehen BoxView
. Der Effect
Code wird dem BoxView
folgenden Code hinzugefügt:
var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);
Die TouchEffect
Klasse ist eine RoutingEffect
Klasse, die auf jeder Plattform von einer TouchEffect
Klasse implementiert wird, die eine PlatformEffect
ist. Die Plattformklasse TouchEffect
stellt die Funktionalität zum Ziehen der BoxView
Seite bereit. Weitere Informationen zu diesen Effektklassen finden Sie unter Aufrufen von Ereignissen aus Effekten.
Auf allen Plattformen weist die TouchEffect
Klasse den folgenden Konstruktor auf, der ein ILogger
Argument erfordert:
public TouchEffect(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Auf allen Plattformen erfolgt die Typregistrierung mit dem Abhängigkeitseinfügungscontainer durch die RegisterTypes
Methode, die vor dem Laden der Anwendung mit der LoadApplication(new App())
Methode aufgerufen wird. Das folgende Beispiel zeigt die RegisterTypes
Methode auf der Android-Plattform:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<TouchTracking.Droid.TouchEffect>();
App.BuildContainer();
}
In diesem Beispiel wird der Logger
konkrete Typ über eine Zuordnung mit seinem Schnittstellentyp registriert, und der TouchEffect
Typ wird direkt ohne Schnittstellenzuordnung registriert. Wenn der Benutzer zu der Seite navigiert, die eine BoxView
Instanz enthält, die der TouchEffect
Instanz zugeordnet ist, wird die Methode zur Auflösung der Abhängigkeitsauflösung aufgerufen, um den Plattformtyp TouchEffect
aus dem Container zum Einfügen von Abhängigkeiten aufzulösen und in den TouchEffect
Konstruktor einzufügenLogger
.
Registrieren von DependencyService-Implementierungen
Die Beispielanwendung enthält eine Seite, auf der Implementierungen auf jeder Plattform verwendet DependencyService
werden, damit der Benutzer ein Foto aus der Bildbibliothek des Geräts auswählen kann. Die IPhotoPicker
Schnittstelle definiert die Funktionalität, die von den DependencyService
Implementierungen implementiert wird, und wird im folgenden Beispiel gezeigt:
public interface IPhotoPicker
{
Task<Stream> GetImageStreamAsync();
}
In jedem Plattformprojekt implementiert die Klasse die PhotoPicker
IPhotoPicker
Schnittstelle mithilfe von Plattform-APIs. Weitere Informationen zu diesen Abhängigkeitsdiensten finden Sie unter Auswählen eines Fotos aus der Bildbibliothek.
Unter iOS und UWP weisen die PhotoPicker
Klassen den folgenden Konstruktor auf, der ein ILogger
Argument erfordert:
public PhotoPicker(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Auf allen Plattformen erfolgt die Typregistrierung mit dem Abhängigkeitseinfügungscontainer durch die RegisterTypes
Methode, die vor dem Laden der Anwendung mit der LoadApplication(new App())
Methode aufgerufen wird. Das folgende Beispiel zeigt die RegisterTypes
Methode für UWP:
void RegisterTypes()
{
DIContainerDemo.App.RegisterType<ILogger, Logger>();
DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
DIContainerDemo.App.BuildContainer();
}
In diesem Beispiel wird der Logger
konkrete Typ über eine Zuordnung mit seinem Schnittstellentyp registriert, und der PhotoPicker
Typ wird auch über eine Schnittstellenzuordnung registriert.
Der PhotoPicker
Konstruktor auf der Android-Plattform ist etwas komplizierter, da es zusätzlich zum ILogger
Argument ein Context
Argument erfordert:
public PhotoPicker(Context context, ILogger logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Das folgende Beispiel zeigt die RegisterTypes
Methode auf der Android-Plattform:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
In diesem Beispiel registriert die App.RegisterTypeWithParameters
Methode den PhotoPicker
Container zum Einfügen von Abhängigkeiten. Die Registrierungsmethode stellt sicher, dass die MainActivity
Instanz als Context
Argument eingefügt wird und dass der Logger
Typ als ILogger
Argument eingefügt wird.
Wenn der Benutzer zur Fotoauswahlseite navigiert und ein Foto auswählt, wird der OnSelectPhotoButtonClicked
Handler ausgeführt:
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);
}
...
}
Wenn die DependencyService.Resolve<T>
Methode aufgerufen wird, wird die Abhängigkeitsauflösungsmethode aufgerufen, um den Typ aus dem PhotoPicker
Container zum Einfügen von Abhängigkeiten aufzulösen, wodurch der Logger
Typ auch aufgelöst und in den PhotoPicker
Konstruktor eingefügt wird.
Hinweis
Die Resolve<T>
Methode muss beim Auflösen eines Typs aus dem Container zum Einfügen von Abhängigkeiten der Anwendung über den DependencyService
.