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 EffectContainers verwaltet, Xamarin.Forms muss die Methode für die Abhängigkeitsauflösung ebenfalls bekannt sein, bevor versucht wird, eine Ansicht zu erstellen, die diese Effectverwendet.

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 PlatformEffectist. 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 PhotoPickerIPhotoPicker 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.