Abhängigkeitsauflösung in Xamarin.Forms

Beispiel herunterladen Das Beispiel herunterladen

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 Effectverwaltet, Xamarin.Forms muss die Abhängigkeitsauflösungsmethode bekannt sein, bevor versucht wird, eine Ansicht zu erstellen, die diese Effectverwendet.

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 DependencyServicewird.