Abhängigkeitsauflösung in Xamarin.Forms
In diesem Artikel wird erläutert, wie Eine Abhängigkeitsauflösungsmethode in Xamarin.Forms eingefügt wird, damit der Dependency Injection-Container einer Anwendung die Kontrolle über die Erstellung und Lebensdauer von benutzerdefinierten Renderern, Effekten und DependencyService-Implementierungen hat. Die Codebeispiele in diesem Artikel stammen aus dem Beispiel zur Abhängigkeitsauflösung mithilfe von Containern .
Im Kontext einer Xamarin.Forms Anwendung, die das Model-View-ViewModel-Muster (MVVM) verwendet, kann ein Abhängigkeitsinjektionscontainer zum Registrieren und Auflösen von Ansichtsmodellen sowie zum Registrieren von Diensten und zum Einfügen dieser Injektion 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 auf. Weitere Informationen zur Abhängigkeitsinjektion, einschließlich Beispielen zum Einfügen von Abhängigkeiten in Ansichtsmodelle, finden Sie unter Abhängigkeitsinjektion.
Die Kontrolle über die Erstellung und Lebensdauer von Typen in Plattformprojekten wird in der Regel von Xamarin.Formsausgeführt, wobei die Activator.CreateInstance
-Methode verwendet wird, um Instanzen von benutzerdefinierten Renderern, Effekten und DependencyService
Implementierungen zu erstellen. Leider schränkt dies die Entwicklerkontrolle über die Erstellung und Lebensdauer dieser Typen und die Möglichkeit ein, Abhängigkeiten in sie einzufügen. Dieses Verhalten kann geändert werden, indem eine Abhängigkeitsauflösungsmethode in Xamarin.Forms eingefügt wird, die steuert, wie Typen erstellt werden – entweder durch den Dependency Injection-Container der Anwendung oder durch Xamarin.Forms. Beachten Sie jedoch, dass keine Abhängigkeitsauflösungsmethode in Xamarin.Formseingefügt werden muss. 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, Xamarin.Forms die registrierte Typen mithilfe eines Abhängigkeitsinjektionscontainers auflöst, aber es ist auch möglich, eine Abhängigkeitsauflösungsmethode einzufügen, die Factorymethoden verwendet, um registrierte Typen aufzulösen. Weitere Informationen finden Sie im Beispiel zur Abhängigkeitsauflösung mithilfe von Factorymethoden .
Einfügen einer Abhängigkeitsauflösungsmethode
Die DependencyResolver
-Klasse bietet die Möglichkeit, eine Abhängigkeitsauflösungsmethode mithilfe der ResolveUsing
-Methode in Xamarin.Formseinzufügen. Wenn Xamarin.Forms dann eine instance eines bestimmten Typs benötigt, erhält die Abhängigkeitsauflösungsmethode die Möglichkeit, die instance bereitzustellen. Wenn die Abhängigkeitsauflösungsmethode für einen angeforderten Typ zurückgibtnull
, greift auf den Versuch zurück, Xamarin.Forms den Typ instance sich 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 Lambdaausdruck festgelegt, der den Container autofac dependency injection verwendet, um alle Typen aufzulösen, die im Container registriert wurden. Andernfalls wird zurückgegeben, was dazu führtXamarin.Forms, dass versucht wird, null
den Typ aufzulösen.
Hinweis
Die api, die von einem Abhängigkeitsinjektionscontainer verwendet wird, ist spezifisch für den Container. In den Codebeispielen in diesem Artikel wird Autofac als Abhängigkeitsinjektionscontainer verwendet, der die IContainer
Typen und ContainerBuilder
bereitstellt. Alternative Abhängigkeitsinjektionscontainer könnten ebenfalls verwendet werden, würden aber andere APIs als hier dargestellt verwenden.
Beachten Sie, dass es nicht erforderlich ist, die Abhängigkeitsauflösungsmethode während des Anwendungsstarts festzulegen. Sie kann jederzeit festgelegt werden. Die einzige Einschränkung ist, dass Xamarin.Forms die Abhängigkeitsauflösungsmethode zu dem Zeitpunkt kennen muss, zu dem die Anwendung versucht, typen zu nutzen, die im Dependency Injection-Container gespeichert sind. Wenn im Container für die Abhängigkeitsinjektion Dienste vorhanden sind, die die Anwendung während des Startvorgangs benötigt, muss die Abhängigkeitsauflösungsmethode daher zu einem frühen Zeitpunkt im Lebenszyklus der Anwendung festgelegt werden. Wenn der Dependency Injection-Container die Erstellung und Lebensdauer eines bestimmten Effect
verwaltet, Xamarin.Forms muss die Abhängigkeitsauflösungsmethode bekannt sein, bevor versucht wird, eine Ansicht zu erstellen, die diese Effect
verwendet.
Warnung
Das Registrieren und Auflösen von Typen mit einem Abhängigkeitsinjektionscontainer verursacht Leistungseinbußen aufgrund der Verwendung der Reflektion des Containers zum Erstellen der einzelnen Typen, insbesondere wenn Abhängigkeiten für jede Seitennavigation in der Anwendung wiederhergestellt 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 Dependency Injection-Container 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 Typregistrierungen in der Regel aus Plattformprojekten durchgeführt. Dadurch 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, was durch Aufrufen der BuildContainer
-Methode erreicht wird. Diese Methode ruft die Autofac-Methode Build
auf der ContainerBuilder
instance auf, die einen neuen Container für die Abhängigkeitsinjektion erstellt, der die vorgenommenen Registrierungen enthält.
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 benutzerdefinierter Renderer
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 von einer VideoPlayerRenderer
Klasse implementiert, die die Funktionen zum Wiedergeben des Videos bereitstellt. Weitere Informationen zu diesen benutzerdefinierten Rendererklassen finden Sie unter Implementieren eines Videoplayers.
Unter iOS und dem Universelle Windows-Plattform (UWP) verfügen die VideoPlayerRenderer
Klassen über den folgenden Konstruktor, der ein ILogger
Argument erfordert:
public VideoPlayerRenderer(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Auf allen Plattformen wird die Typregistrierung mit dem Dependency Injection-Container von der RegisterTypes
-Methode ausgeführt, die aufgerufen wird, bevor die Plattform die Anwendung mit der LoadApplication(new App())
-Methode lädt. 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 für seinen 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 Dependency Injection-Container aufzulösen. Dadurch wird auch der Logger
Typ aufgelöst und in den VideoPlayerRenderer
Konstruktor eingefügt.
Der VideoPlayerRenderer
Konstruktor auf der Android-Plattform ist etwas komplizierter, da er 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
beim Abhängigkeitsinjektionscontainer. Die Registrierungsmethode stellt sicher, dass die MainActivity
instance 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 auf die Seite zu ziehen BoxView
. Der Effect
wird mithilfe des BoxView
folgenden Codes hinzugefügt:
var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);
Die TouchEffect
-Klasse ist eine RoutingEffect
, die auf jeder Plattform von einer TouchEffect
-Klasse implementiert wird, bei der es sich um eine -Klasse handelt PlatformEffect
. Die Plattformklasse TouchEffect
bietet die Funktionalität zum Ziehen von BoxView
um die Seite. Weitere Informationen zu diesen Effektklassen finden Sie unter Aufrufen von Ereignissen aus Effekten.
Auf allen Plattformen verfügt die TouchEffect
-Klasse über den folgenden Konstruktor, der ein ILogger
Argument erfordert:
public TouchEffect(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Auf allen Plattformen wird die Typregistrierung mit dem Dependency Injection-Container von der RegisterTypes
-Methode ausgeführt, die aufgerufen wird, bevor die Plattform die Anwendung mit der LoadApplication(new App())
-Methode lädt. 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 für seinen Schnittstellentyp registriert, und der TouchEffect
Typ wird direkt ohne Schnittstellenzuordnung registriert. Wenn der Benutzer zu der Seite navigiert, die eine BoxView
instance enthält, an die der TouchEffect
angefügt ist, wird die Abhängigkeitsauflösungsmethode aufgerufen, um den Plattformtyp TouchEffect
aus dem Dependency Injection-Container aufzulösen, der den Logger
Typ ebenfalls auflöst und in den TouchEffect
Konstruktor einfügt.
Registrieren von DependencyService-Implementierungen
Die Beispielanwendung enthält eine Seite, die Implementierungen auf jeder Plattform verwendet DependencyService
, 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 PhotoPicker
-Klasse die -Schnittstelle mithilfe von IPhotoPicker
Plattform-APIs. Weitere Informationen zu diesen Abhängigkeitsdiensten finden Sie unter Auswählen eines Fotos aus der Bildbibliothek.
Unter iOS und UWP verfügen die PhotoPicker
Klassen über den folgenden Konstruktor, der ein ILogger
Argument erfordert:
public PhotoPicker(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Auf allen Plattformen wird die Typregistrierung mit dem Dependency Injection-Container von der RegisterTypes
-Methode ausgeführt, die aufgerufen wird, bevor die Plattform die Anwendung mit der LoadApplication(new App())
-Methode lädt. Das folgende Beispiel zeigt die RegisterTypes
-Methode auf 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 zu seinem Schnittstellentyp registriert, und der PhotoPicker
Typ wird auch über eine Schnittstellenzuordnung registriert.
Der PhotoPicker
Konstruktor auf der Android-Plattform ist etwas komplizierter, da er 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
beim Abhängigkeitsinjektionscontainer. Die Registrierungsmethode stellt sicher, dass die MainActivity
instance 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 PhotoPicker
Typ aus dem Dependency Injection-Container aufzulösen, wodurch auch der Typ aufgelöst und in Logger
den PhotoPicker
Konstruktor eingefügt wird.
Hinweis
Die Resolve<T>
-Methode muss verwendet werden, wenn ein Typ aus dem Abhängigkeitsinjektionscontainer der Anwendung über aufgelöst DependencyService
wird.