Ř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 DependencyService
injektáže závislostí aplikace prostřednictvím .