Rozwiązywanie zależności w programie Xamarin.Forms
W tym artykule wyjaśniono, jak wstrzyknąć metodę rozwiązywania zależności do Xamarin.Forms kontenera wstrzykiwania zależności aplikacji ma kontrolę nad tworzeniem i okresem istnienia niestandardowych modułów renderujących, efektów i implementacji DependencyService.
W kontekście Xamarin.Forms aplikacji korzystającej ze wzorca Model-View-ViewModel (MVVM) kontener wstrzykiwania zależności może służyć do rejestrowania i rozpoznawania modeli widoków oraz rejestrowania usług i wstrzykiwania ich do modeli widoków. Podczas tworzenia modelu widoku kontener wprowadza wszelkie wymagane zależności. Jeśli te zależności nie zostały utworzone, kontener tworzy i rozpoznaje zależności jako pierwsze. Aby uzyskać więcej informacji na temat wstrzykiwania zależności, w tym przykłady wstrzykiwania zależności do modeli widoków, zobacz Wstrzykiwanie zależności.
Kontrola nad tworzeniem i okresem istnienia typów w projektach platformy jest tradycyjnie wykonywana przez Xamarin.Formsprogram , który używa Activator.CreateInstance
metody do tworzenia wystąpień niestandardowych modułów renderujących, efektów i DependencyService
implementacji. Niestety, ogranicza to kontrolę deweloperów nad tworzeniem i okresem istnienia tych typów oraz możliwość wstrzykiwania do nich zależności. To zachowanie można zmienić, wstrzykiwając metodę rozwiązywania zależności do Xamarin.Forms tej metody, która kontroluje sposób tworzenia typów — przez kontener iniekcji zależności aplikacji lub przez Xamarin.Forms. Należy jednak pamiętać, że nie ma potrzeby wstrzykiwania metody rozwiązywania zależności do Xamarin.Formsmetody . Xamarin.Forms program będzie nadal tworzyć typy typów w projektach platformy i zarządzać nimi, jeśli metoda rozwiązywania zależności nie zostanie wstrzyknięta.
Uwaga
Chociaż w tym artykule skupiono się na wstrzyknięciu metody rozwiązywania zależności w Xamarin.Forms celu rozpoznawania zarejestrowanych typów przy użyciu kontenera wstrzykiwania zależności, możliwe jest również wstrzyknięcie metody rozwiązywania zależności, która używa metod fabrycznych do rozpoznawania zarejestrowanych typów.
Wstrzykiwanie metody rozwiązywania zależności
Klasa DependencyResolver
zapewnia możliwość wstrzykiwania metody rozwiązywania zależności do Xamarin.Formsmetody przy użyciu ResolveUsing
metody . Następnie, gdy Xamarin.Forms potrzebne jest wystąpienie określonego typu, metoda rozpoznawania zależności ma możliwość udostępnienia wystąpienia. Jeśli metoda rozwiązywania zależności zwraca null
żądany typ, Xamarin.Forms powraca do próby utworzenia samego wystąpienia typu przy użyciu Activator.CreateInstance
metody .
W poniższym przykładzie pokazano, jak ustawić metodę rozpoznawania zależności za pomocą ResolveUsing
metody :
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);
...
}
...
}
W tym przykładzie metoda rozpoznawania zależności jest ustawiona na wyrażenie lambda, które używa kontenera iniekcji zależności Autofac do rozpoznawania wszystkich typów zarejestrowanych w kontenerze. null
W przeciwnym razie zostanie zwrócona wartość , co spowoduje Xamarin.Forms próbę rozpoznania typu.
Uwaga
Interfejs API używany przez kontener wstrzykiwania zależności jest specyficzny dla kontenera. Przykłady kodu w tym artykule używają funkcji Autofac jako kontenera iniekcji zależności, który udostępnia IContainer
typy i ContainerBuilder
. Alternatywne kontenery iniekcji zależności mogą być równie używane, ale mogą używać różnych interfejsów API, niż przedstawiono tutaj.
Należy pamiętać, że podczas uruchamiania aplikacji nie ma potrzeby ustawiania metody rozwiązywania zależności. Można go ustawić w dowolnym momencie. Jedynym ograniczeniem jest to, że Xamarin.Forms musi wiedzieć o metodzie rozwiązywania zależności przez czas, gdy aplikacja próbuje korzystać z typów przechowywanych w kontenerze wstrzykiwania zależności. W związku z tym, jeśli w kontenerze wstrzykiwania zależności istnieją usługi, których aplikacja będzie wymagać podczas uruchamiania, metoda rozpoznawania zależności będzie musiała zostać ustawiona na wczesnym etapie cyklu życia aplikacji. Podobnie, jeśli kontener iniekcji zależności zarządza tworzeniem i okresem istnienia określonego Effect
elementu , Xamarin.Forms będzie musiał wiedzieć o metodzie rozwiązywania zależności, zanim podejmie próbę utworzenia widoku, który używa tego Effect
elementu .
Ostrzeżenie
Rejestrowanie i rozpoznawanie typów za pomocą kontenera iniekcji zależności ma koszt wydajności ze względu na użycie odbicia kontenera do tworzenia każdego typu, zwłaszcza jeśli zależności są rekonstruowane dla każdej nawigacji na stronie w aplikacji. Jeśli istnieje wiele lub głębokie zależności, koszt tworzenia może znacznie wzrosnąć.
Rejestrowanie typów
Typy muszą być zarejestrowane w kontenerze iniekcji zależności, aby można je było rozpoznać za pomocą metody rozwiązywania zależności. Poniższy przykład kodu przedstawia metody rejestracji, które przykładowa aplikacja uwidacznia w App
klasie dla kontenera 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();
}
...
}
Gdy aplikacja używa metody rozwiązywania zależności do rozpoznawania typów z kontenera, rejestracje typów są zwykle wykonywane z projektów platformy. Dzięki temu projekty platformy mogą rejestrować typy niestandardowych renderatorów, efektów i DependencyService
implementacji.
Po rejestracji typu z projektu IContainer
platformy należy skompilować obiekt, który jest osiągany przez wywołanie BuildContainer
metody . Ta metoda wywołuje metodę Autofac w Build
wystąpieniu ContainerBuilder
, która tworzy nowy kontener iniekcji zależności, który zawiera rejestracje, które zostały wykonane.
W kolejnych sekcjach klasa implementujący Logger
ILogger
interfejs jest wstrzykiwana do konstruktorów klas. Klasa Logger
implementuje proste funkcje rejestrowania przy użyciu Debug.WriteLine
metody i służy do zademonstrowania sposobu wstrzykiwania usług do niestandardowych modułów renderujących, efektów i DependencyService
implementacji.
Rejestrowanie niestandardowych modułów renderujących
Przykładowa aplikacja zawiera stronę, która odtwarza wideo internetowe, których źródło XAML zostało pokazane w poniższym przykładzie:
<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>
Widok VideoPlayer
jest implementowany na każdej platformie przez klasę VideoPlayerRenderer
, która zapewnia funkcjonalność odtwarzania filmu wideo. Aby uzyskać więcej informacji na temat tych niestandardowych klas renderowania, zobacz Implementowanie odtwarzacza wideo.
W systemach iOS i platforma uniwersalna systemu Windows (UWP) VideoPlayerRenderer
klasy mają następujący konstruktor, który wymaga argumentuILogger
:
public VideoPlayerRenderer(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Na wszystkich platformach rejestracja typu w kontenerze iniekcji zależności jest wykonywana przez RegisterTypes
metodę, która jest wywoływana przed załadowaniem aplikacji do platformy za LoadApplication(new App())
pomocą metody . Poniższy przykład przedstawia metodę RegisterTypes
na platformie iOS:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
App.BuildContainer();
}
W tym przykładzie typ betonu Logger
jest rejestrowany za pośrednictwem mapowania względem jego typu interfejsu, a VideoPlayerRenderer
typ jest rejestrowany bezpośrednio bez mapowania interfejsu. Gdy użytkownik przejdzie do strony zawierającej VideoPlayer
widok, metoda rozwiązywania zależności zostanie wywołana w celu rozpoznania VideoPlayerRenderer
typu z kontenera iniekcji zależności, który również rozpozna i wstrzykuje Logger
typ do konstruktora VideoPlayerRenderer
.
Konstruktor VideoPlayerRenderer
na platformie Android jest nieco bardziej skomplikowany, ponieważ wymaga Context
argumentu oprócz argumentu ILogger
:
public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Poniższy przykład przedstawia metodę RegisterTypes
na platformie Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
W tym przykładzie metoda rejestruje VideoPlayerRenderer
element App.RegisterTypeWithParameters
w kontenerze iniekcji zależności. Metoda rejestracji gwarantuje, że MainActivity
wystąpienie zostanie wstrzyknięte jako Context
argument i że Logger
typ zostanie wstrzyknięty jako ILogger
argument.
Rejestrowanie efektów
Przykładowa aplikacja zawiera stronę, która używa efektu śledzenia dotykowego do przeciągania BoxView
wystąpień wokół strony. Element Effect
zostanie dodany do elementu BoxView
przy użyciu następującego kodu:
var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);
Klasa TouchEffect
jest zaimplementowana RoutingEffect
na każdej platformie przez klasę , która jest klasą TouchEffect
PlatformEffect
. Klasa platformy TouchEffect
udostępnia funkcje przeciągania BoxView
wokół strony. Aby uzyskać więcej informacji na temat tych klas efektów, zobacz Wywoływanie zdarzeń z efektów.
Na wszystkich platformach TouchEffect
klasa ma następujący konstruktor, który wymaga argumentu ILogger
:
public TouchEffect(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Na wszystkich platformach rejestracja typu w kontenerze iniekcji zależności jest wykonywana przez RegisterTypes
metodę, która jest wywoływana przed załadowaniem aplikacji do platformy za LoadApplication(new App())
pomocą metody . Poniższy przykład przedstawia metodę RegisterTypes
na platformie Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<TouchTracking.Droid.TouchEffect>();
App.BuildContainer();
}
W tym przykładzie typ betonu Logger
jest rejestrowany za pośrednictwem mapowania względem jego typu interfejsu, a TouchEffect
typ jest rejestrowany bezpośrednio bez mapowania interfejsu. Gdy użytkownik przejdzie do strony zawierającej BoxView
wystąpienie, które ma TouchEffect
dołączone do niego wystąpienie, zostanie wywołana metoda rozpoznawania zależności w celu rozpoznania typu platformy TouchEffect
z kontenera wstrzykiwania zależności, który również rozpozna i wstrzykuje Logger
typ do konstruktora TouchEffect
.
Rejestrowanie implementacji usługi DependencyService
Przykładowa aplikacja zawiera stronę, która używa DependencyService
implementacji na każdej platformie, aby umożliwić użytkownikowi wybranie zdjęcia z biblioteki obrazów urządzenia. Interfejs IPhotoPicker
definiuje funkcje implementowane przez DependencyService
implementacje i pokazano w poniższym przykładzie:
public interface IPhotoPicker
{
Task<Stream> GetImageStreamAsync();
}
W każdym projekcie platformy PhotoPicker
klasa implementuje IPhotoPicker
interfejs przy użyciu interfejsów API platformy. Aby uzyskać więcej informacji na temat tych usług zależności, zobacz Wybieranie zdjęcia z biblioteki obrazów.
W systemach iOS i UWP PhotoPicker
klasy mają następujący konstruktor, który wymaga argumentu ILogger
:
public PhotoPicker(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Na wszystkich platformach rejestracja typu w kontenerze iniekcji zależności jest wykonywana przez RegisterTypes
metodę, która jest wywoływana przed załadowaniem aplikacji do platformy za LoadApplication(new App())
pomocą metody . W poniższym przykładzie przedstawiono metodę RegisterTypes
w systemie UWP:
void RegisterTypes()
{
DIContainerDemo.App.RegisterType<ILogger, Logger>();
DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
DIContainerDemo.App.BuildContainer();
}
W tym przykładzie typ betonowy Logger
jest rejestrowany za pośrednictwem mapowania względem jego typu interfejsu, a PhotoPicker
typ jest również rejestrowany za pośrednictwem mapowania interfejsu.
Konstruktor PhotoPicker
na platformie Android jest nieco bardziej skomplikowany, ponieważ wymaga Context
argumentu oprócz argumentu ILogger
:
public PhotoPicker(Context context, ILogger logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Poniższy przykład przedstawia metodę RegisterTypes
na platformie Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
W tym przykładzie metoda rejestruje PhotoPicker
element App.RegisterTypeWithParameters
w kontenerze iniekcji zależności. Metoda rejestracji gwarantuje, że MainActivity
wystąpienie zostanie wstrzyknięte jako Context
argument i że Logger
typ zostanie wstrzyknięty jako ILogger
argument.
Gdy użytkownik przejdzie do strony wybierania zdjęć i wybierze zdjęcie, OnSelectPhotoButtonClicked
procedura obsługi jest wykonywana:
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>
Po wywołaniu metody metoda rozpoznawania zależności zostanie wywołana w celu rozpoznania PhotoPicker
typu z kontenera wstrzykiwania zależności, który również rozpozna i wstrzykuje Logger
typ do konstruktoraPhotoPicker
.
Uwaga
Metoda Resolve<T>
musi być używana podczas rozpoznawania typu z kontenera iniekcji zależności aplikacji za pośrednictwem .DependencyService