Sdílet prostřednictvím


Řešení závislostí v Xamarin.Forms

Tento článek vysvětluje, jak vložit metodu řešení závislostí tak Xamarin.Forms , aby kontejner injektáž závislostí aplikace měl kontrolu nad vytvořením a životností vlastních rendererů, efektů a implementací DependencyService.

V kontextu Xamarin.Forms aplikace, která používá model Model-View-ViewModel (MVVM), lze kontejner injektáž závislostí použít k registraci a překladu modelů zobrazení a k registraci služeb a jejich vkládání do zobrazení modelů. Během vytváření modelu zobrazení kontejner vloží všechny požadované závislosti. Pokud se tyto závislosti nevytvořily, kontejner nejprve vytvoří a přeloží závislosti. Další informace o injektáži závislostí, včetně příkladů vkládání závislostí do zobrazení modelů, naleznete v tématu Injektáž závislostí.

Kontrola nad vytvářením a životností typů v projektech platformy se tradičně provádí Xamarin.Forms, která používá metodu Activator.CreateInstance k vytváření instancí vlastních rendererů, efektů a DependencyService implementací. To bohužel omezuje kontrolu nad vytvářením a životností těchto typů a schopností vkládat do nich závislosti. Toto chování lze změnit vložením metody řešení závislostí, která Xamarin.Forms řídí způsob vytváření typů – buď kontejnerem injektáže závislostí aplikace, nebo pomocí Xamarin.Forms. Mějte však na paměti, že neexistuje žádný požadavek na vložení metody řešení závislostí do Xamarin.Forms. Xamarin.Forms bude nadále vytvářet a spravovat životnost typů v projektech platformy, pokud není vložena metoda řešení závislostí.

Poznámka:

Tento článek se zaměřuje na vložení metody řešení závislostí do Xamarin.Forms řešení, která řeší registrované typy pomocí kontejneru injektáže závislostí, je také možné vložit metodu řešení závislostí, která používá metody továrny k vyřešení registrovaných typů.

Vložení metody řešení závislostí

Třída DependencyResolver poskytuje možnost vložit metodu řešení závislostí do Xamarin.Forms, pomocí ResolveUsing metody. Xamarin.Forms Když pak potřebujete instanci určitého typu, metoda řešení závislostí má možnost poskytnout instanci. Pokud metoda řešení závislostí vrátí null požadovaný typ, Xamarin.Forms vrátí se k pokusu o vytvoření samotné instance typu pomocí Activator.CreateInstance metody.

Následující příklad ukazuje, jak nastavit metodu řešení závislostí s metodou ResolveUsing :

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);
        ...
    }
    ...
}

V tomto příkladu je metoda řešení závislostí nastavena na výraz lambda, který používá kontejner injektáže závislostí autofac k vyřešení všech typů zaregistrovaných v kontejneru. V opačném případě se vrátí, null což způsobí Xamarin.Forms pokus o vyřešení typu.

Poznámka:

Rozhraní API používané kontejnerem injektáže závislostí je specifické pro kontejner. Příklady kódu v tomto článku používají autofac jako kontejner injektáž závislostí, který poskytuje a IContainer ContainerBuilder typy. Kontejnery injektáže alternativních závislostí lze použít stejně, ale používaly by jiná rozhraní API, než jsou zde uvedeny.

Všimněte si, že během spouštění aplikace není nutné nastavit metodu řešení závislostí. Můžete ho kdykoli nastavit. Jediným omezením je, že Xamarin.Forms potřebuje vědět o metodě řešení závislostí v době, kdy se aplikace pokouší využívat typy uložené v kontejneru injektáže závislostí. Pokud tedy v kontejneru injektáže závislostí existují služby, které bude aplikace během spouštění vyžadovat, bude nutné v rané fázi životního cyklu aplikace nastavit metodu řešení závislostí. Podobně, pokud kontejner injektáž závislostí spravuje vytváření a životnost konkrétního Effect, Xamarin.Forms bude muset vědět o metodě řešení závislostí dříve, než se pokusí vytvořit zobrazení, které používá .Effect

Upozorňující

Registrace a řešení typů v kontejneru injektáže závislostí má náklady na výkon kvůli použití reflexe kontejneru při vytváření jednotlivých typů, zejména pokud jsou závislosti rekonstruovány pro každou navigaci na stránce v aplikaci. Pokud existuje mnoho nebo hlubokých závislostí, mohou se náklady na vytvoření výrazně zvýšit.

Registrace typů

Typy musí být zaregistrované v kontejneru injektáže závislostí, aby je bylo možné přeložit pomocí metody řešení závislostí. Následující příklad kódu ukazuje metody registrace, které ukázková aplikace zveřejňuje ve App třídě pro kontejner 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();
    }
    ...
}

Pokud aplikace k překladu typů z kontejneru používá metodu řešení závislostí, registrace typů se obvykle provádějí z projektů platformy. To umožňuje projektům platformy registrovat typy pro vlastní renderery, efekty a DependencyService implementace.

Po registraci typu z projektu IContainer platformy musí být objekt vytvořen, což se provádí voláním BuildContainer metody. Tato metoda vyvolá metodu Autofac Build v ContainerBuilder instanci, která vytvoří nový kontejner injektáže závislostí, který obsahuje provedené registrace.

V následujících částech se Logger třída, která implementuje ILogger rozhraní, vloží do konstruktorů tříd. Třída Logger implementuje jednoduchou funkci protokolování pomocí Debug.WriteLine metody a slouží k předvedení způsobu vkládání služeb do vlastních rendererů, efektů a DependencyService implementací.

Registrace vlastních rendererů

Ukázková aplikace obsahuje stránku, která přehrává webová videa, jejíž zdroj XAML je zobrazený v následujícím příkladu:

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

Zobrazení VideoPlayer je implementováno VideoPlayerRenderer na každé platformě třídou, která poskytuje funkce pro přehrávání videa. Další informace o těchto vlastních třídách rendereru naleznete v tématu Implementace přehrávače videa.

V iOSu a Univerzální platforma Windows (UPW) VideoPlayerRenderer mají třídy následující konstruktor, který vyžaduje ILogger argument:

public VideoPlayerRenderer(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

Na všech platformách se registrace typu kontejneru injektáže závislostí provádí RegisterTypes metodou, která se vyvolá před platformou, která načítá aplikaci metodou LoadApplication(new App()) . Následující příklad ukazuje metodu RegisterTypes na platformě iOS:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
    App.BuildContainer();
}

V tomto příkladu Logger je konkrétní typ registrován prostřednictvím mapování na jeho typ rozhraní a VideoPlayerRenderer typ je registrován přímo bez mapování rozhraní. Když uživatel přejde na stránku obsahující VideoPlayer zobrazení, vyvolá se metoda řešení závislostí, která přeloží VideoPlayerRenderer typ z kontejneru injektáže závislostí, který také přeloží a vloží Logger typ do konstruktoru VideoPlayerRenderer .

Konstruktor VideoPlayerRenderer na platformě Android je o něco složitější, protože kromě argumentu ILogger vyžaduje Context argument:

public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

Následující příklad ukazuje metodu RegisterTypes na platformě Android:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

V tomto příkladu metoda App.RegisterTypeWithParameters zaregistruje VideoPlayerRenderer kontejner injektáže závislostí. Metoda registrace zajišťuje, že MainActivity instance bude vložena jako Context argument a že Logger typ bude vložen jako ILogger argument.

Registrace efektů

Ukázková aplikace obsahuje stránku, která používá efekt dotykového sledování k přetažení BoxView instancí kolem stránky. Tento Effect kód se přidá do BoxView následujícího kódu:

var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);

Třída TouchEffect je, RoutingEffect která je implementována na každé platformě TouchEffect třídou, která je .PlatformEffect Třída platformy TouchEffect poskytuje funkce pro přetahování BoxView kolem stránky. Další informace o těchto třídách efektů naleznete v tématu Vyvolání událostí z efektů.

Na všech platformách TouchEffect má třída následující konstruktor, který vyžaduje ILogger argument:

public TouchEffect(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

Na všech platformách se registrace typu kontejneru injektáže závislostí provádí RegisterTypes metodou, která se vyvolá před platformou, která načítá aplikaci metodou LoadApplication(new App()) . Následující příklad ukazuje metodu RegisterTypes na platformě Android:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<TouchTracking.Droid.TouchEffect>();
    App.BuildContainer();
}

V tomto příkladu Logger je konkrétní typ registrován prostřednictvím mapování na jeho typ rozhraní a TouchEffect typ je registrován přímo bez mapování rozhraní. Když uživatel přejde na stránku obsahující BoxView instanci, která TouchEffect je k ní připojená, vyvolá se metoda řešení závislostí za účelem vyřešení typu platformy TouchEffect z kontejneru injektáže závislostí, který také přeloží a vloží Logger typ do konstruktoru TouchEffect .

Registrace implementací DependencyService

Ukázková aplikace obsahuje stránku, která používá DependencyService implementace na jednotlivých platformách, aby uživatel mohl vybrat fotku z knihovny obrázků zařízení. Rozhraní IPhotoPicker definuje funkce implementované implementacemi DependencyService a je znázorněno v následujícím příkladu:

public interface IPhotoPicker
{
    Task<Stream> GetImageStreamAsync();
}

V každém projektu PhotoPicker platformy třída implementuje IPhotoPicker rozhraní pomocí rozhraní API platformy. Další informace o těchto závislostech najdete v tématu Výběr fotky z knihovny obrázků.

V iOSu a UPW PhotoPicker mají třídy následující konstruktor, který vyžaduje ILogger argument:

public PhotoPicker(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

Na všech platformách se registrace typu kontejneru injektáže závislostí provádí RegisterTypes metodou, která se vyvolá před platformou, která načítá aplikaci metodou LoadApplication(new App()) . Následující příklad ukazuje metodu RegisterTypes pro UPW:

void RegisterTypes()
{
    DIContainerDemo.App.RegisterType<ILogger, Logger>();
    DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
    DIContainerDemo.App.BuildContainer();
}

V tomto příkladu Logger je konkrétní typ registrován prostřednictvím mapování na jeho typ rozhraní a PhotoPicker typ je také registrován prostřednictvím mapování rozhraní.

Konstruktor PhotoPicker na platformě Android je o něco složitější, protože kromě argumentu ILogger vyžaduje Context argument:

public PhotoPicker(Context context, ILogger logger)
{
    _context = context ?? throw new ArgumentNullException(nameof(context));
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

Následující příklad ukazuje metodu RegisterTypes na platformě Android:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

V tomto příkladu metoda App.RegisterTypeWithParameters zaregistruje PhotoPicker kontejner injektáže závislostí. Metoda registrace zajišťuje, že MainActivity instance bude vložena jako Context argument a že Logger typ bude vložen jako ILogger argument.

Když uživatel přejde na stránku pro výběr fotky a vybere fotku, spustí se OnSelectPhotoButtonClicked obslužná rutina:

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);
    }
    ...
}

DependencyService.Resolve<T> Při vyvolání metody se vyvolá metoda řešení závislostí, která přeloží PhotoPicker typ z kontejneru injektáže závislostí, který také přeloží a vloží Logger typ do konstruktoruPhotoPicker.

Poznámka:

Metoda Resolve<T> musí být použita při překladu typu z kontejneru DependencyServiceinjektáže závislostí aplikace prostřednictvím .