Udostępnij za pośrednictwem


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 Effectelementu , Xamarin.Forms będzie musiał wiedzieć o metodzie rozwiązywania zależności, zanim podejmie próbę utworzenia widoku, który używa tego Effectelementu .

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 LoggerILogger 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ą TouchEffectPlatformEffect. 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