Поделиться через


Разрешение зависимостей в Xamarin.Forms

В этой статье объясняется, как внедрить метод Xamarin.Forms разрешения зависимостей, чтобы контейнер внедрения зависимостей приложения контролировал создание и время существования пользовательских отрисовщиков, эффектов и реализаций DependencyService.

В контексте Xamarin.Forms приложения, использующего шаблон Model-View-ViewModel (MVVM), контейнер внедрения зависимостей можно использовать для регистрации и разрешения моделей представления, а также для регистрации служб и внедрения их в модели просмотра. Во время создания модели представления контейнер внедряет все необходимые зависимости. Если эти зависимости не созданы, контейнер сначала создает и разрешает зависимости. Дополнительные сведения о внедрении зависимостей, включая примеры внедрения зависимостей в модели представления, см. в разделе "Внедрение зависимостей".

Управление созданием и временем существования типов в проектах платформы традиционно выполняется с помощью Xamarin.FormsActivator.CreateInstance метода для создания экземпляров пользовательских отрисовщиков, эффектов и DependencyService реализаций. К сожалению, это ограничивает управление созданием и временем существования этих типов, а также возможность внедрения зависимостей в них. Это поведение можно изменить путем внедрения метода разрешения зависимостей в Xamarin.Forms этот метод управления тем, как будут создаваться типы — контейнером внедрения зависимостей приложения или путем Xamarin.Forms. Однако обратите внимание, что нет необходимости внедрять метод Xamarin.Formsразрешения зависимостей в . Xamarin.Forms будет продолжать создавать и управлять временем существования типов в проектах платформы, если метод разрешения зависимостей не внедряется.

Примечание.

Хотя в этой статье основное внимание уделяется внедрению метода разрешения зависимостей, разрешающего зарегистрированные типы с помощью контейнера внедрения зависимостей, также можно внедрить метод Xamarin.Forms разрешения зависимостей, использующий методы фабрики для разрешения зарегистрированных типов.

Внедрение метода разрешения зависимостей

Класс DependencyResolver предоставляет возможность внедрения метода Xamarin.Formsразрешения зависимостей в метод с помощью ResolveUsing метода. Затем, когда Xamarin.Forms требуется экземпляр определенного типа, метод разрешения зависимостей получает возможность предоставить экземпляр. Если метод разрешения зависимостей возвращается null для запрошенного типа, Xamarin.Forms возвращается к попытке создать экземпляр типа с помощью Activator.CreateInstance метода.

В следующем примере показано, как задать метод разрешения зависимостей с 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);
        ...
    }
    ...
}

В этом примере метод разрешения зависимостей имеет лямбда-выражение, использующее контейнер внедрения зависимостей Autofac для разрешения всех типов, зарегистрированных в контейнере. В противном случае будет возвращено значение null , которое приведет к Xamarin.Forms попытке разрешить тип.

Примечание.

API, используемый контейнером внедрения зависимостей, зависит от контейнера. Примеры кода в этой статье используют Autofac в качестве контейнера внедрения зависимостей, который предоставляет IContainer и ContainerBuilder типы. Альтернативные контейнеры внедрения зависимостей могут использоваться одинаково, но будут использовать различные API, отличные от представленных здесь.

Обратите внимание, что во время запуска приложения не требуется задать метод разрешения зависимостей. Его можно задать в любое время. Единственное ограничение заключается в том, что к тому времени, Xamarin.Forms когда приложение пытается использовать типы, хранящиеся в контейнере внедрения зависимостей, необходимо знать о методе разрешения зависимостей. Таким образом, если в контейнере внедрения зависимостей есть службы, необходимые приложению во время запуска, метод разрешения зависимостей должен быть установлен в начале жизненного цикла приложения. Аналогичным образом, если контейнер внедрения зависимостей управляет созданием и временем существования определенного Effectобъекта, Xamarin.Forms необходимо знать о методе разрешения зависимостей, прежде чем пытаться создать представление, которое использует это Effect.

Предупреждение

Регистрация и разрешение типов в контейнере внедрения зависимостей имеет затраты на производительность из-за использования отражения контейнера для создания каждого типа, особенно если реконструируются зависимости для каждой навигации страницы в приложении. При наличии большого числа зависимостей или глубоких зависимостей стоимость создания может значительно возрасти.

Типы регистрации

Типы должны быть зарегистрированы в контейнере внедрения зависимостей перед их разрешением с помощью метода разрешения зависимостей. В следующем примере кода показаны методы регистрации, предоставляемые примером приложения в App классе для контейнера 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();
    }
    ...
}

Если приложение использует метод разрешения зависимостей для разрешения типов из контейнера, регистрация типов обычно выполняется из проектов платформы. Это позволяет проектам платформы регистрировать типы для пользовательских отрисовщиков, эффектов и DependencyService реализаций.

После регистрации типа из проекта IContainer платформы объект должен быть создан, который выполняется путем вызова BuildContainer метода. Этот метод вызывает метод Autofac Build в экземпляре ContainerBuilder , который создает новый контейнер внедрения зависимостей, содержащий зарегистрированные регистрации.

В следующих разделах класс, реализующий ILogger интерфейс, Logger внедряется в конструкторы классов. Класс Logger реализует простые функции ведения журнала с помощью Debug.WriteLine метода и используется для демонстрации возможности внедрения служб в пользовательские отрисовщики, эффекты и DependencyService реализации.

Регистрация пользовательских отрисовщиков

Пример приложения включает страницу, которая воспроизводит веб-видео, источник XAML которого показан в следующем примере:

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

Представление VideoPlayer реализуется на каждой платформе классом VideoPlayerRenderer , который предоставляет функциональные возможности для воспроизведения видео. Дополнительные сведения об этих пользовательских классах отрисовщика см. в разделе "Реализация видеопроигрывтеля".

В iOS и универсальная платформа Windows (UWP) VideoPlayerRenderer классы имеют следующий конструктор, который требует аргументаILogger:

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

На всех платформах регистрация типов с контейнером внедрения зависимостей выполняется методом RegisterTypes , который вызывается до загрузки приложения с LoadApplication(new App()) помощью метода. В следующем примере показан RegisterTypes метод на платформе iOS:

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

В этом примере Logger конкретный тип регистрируется через сопоставление с его типом интерфейса, а VideoPlayerRenderer тип регистрируется непосредственно без сопоставления интерфейса. Когда пользователь переходит на страницу, содержащую VideoPlayer представление, метод разрешения зависимостей будет вызван для разрешения VideoPlayerRenderer типа из контейнера внедрения зависимостей, который также будет разрешать и внедрять Logger тип в VideoPlayerRenderer конструктор.

Конструктор VideoPlayerRenderer на платформе Android немного сложнее, так как он требует Context аргумента в дополнение к аргументу ILogger :

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

В следующем примере показан RegisterTypes метод на платформе Android:

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

В этом примере App.RegisterTypeWithParameters метод регистрирует контейнер VideoPlayerRenderer внедрения зависимостей. Метод регистрации гарантирует, что MainActivity экземпляр будет введен в качестве аргумента Context , и что Logger тип будет введен в качестве аргумента ILogger .

Регистрация эффектов

Пример приложения содержит страницу, которая использует эффект отслеживания сенсорного ввода для перетаскивания BoxView экземпляров по странице. Добавляется Effect в BoxView следующий код:

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

Класс TouchEffect — это RoutingEffect класс, реализованный на каждой TouchEffect платформе классом, который является классом PlatformEffect. Класс платформы TouchEffect предоставляет функциональные возможности для перетаскивания BoxView вокруг страницы. Дополнительные сведения об этих классах эффектов см. в разделе "Вызов событий из эффектов".

На всех платформах TouchEffect класс имеет следующий конструктор, который требует аргумента ILogger :

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

На всех платформах регистрация типов с контейнером внедрения зависимостей выполняется методом RegisterTypes , который вызывается до загрузки приложения с LoadApplication(new App()) помощью метода. В следующем примере показан RegisterTypes метод на платформе Android:

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

В этом примере Logger конкретный тип регистрируется через сопоставление с его типом интерфейса, а TouchEffect тип регистрируется непосредственно без сопоставления интерфейса. Когда пользователь переходит на страницу, содержащую BoxView экземпляр, TouchEffect подключенный к нему, метод разрешения зависимостей будет вызван для разрешения типа платформы TouchEffect из контейнера внедрения зависимостей, который также будет разрешать и внедрять Logger тип в TouchEffect конструктор.

Регистрация реализаций DependencyService

Пример приложения содержит страницу, которая использует DependencyService реализации на каждой платформе, чтобы разрешить пользователю выбрать фотографию из библиотеки рисунков устройства. Интерфейс IPhotoPicker определяет функциональные возможности, реализованные реализацией DependencyService , и показан в следующем примере:

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

В каждом проекте платформы PhotoPicker класс реализует IPhotoPicker интерфейс с помощью API платформы. Дополнительные сведения об этих службах зависимостей см. в разделе "Выбор фотографии" из библиотеки рисунков.

В iOS и UWP PhotoPicker классы имеют следующий конструктор, который требует аргумента ILogger :

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

На всех платформах регистрация типов с контейнером внедрения зависимостей выполняется методом RegisterTypes , который вызывается до загрузки приложения с LoadApplication(new App()) помощью метода. В следующем примере показан RegisterTypes метод в UWP:

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

В этом примере Logger конкретный тип регистрируется через сопоставление с его типом интерфейса, а PhotoPicker тип также регистрируется с помощью сопоставления интерфейса.

Конструктор PhotoPicker на платформе Android немного сложнее, так как он требует Context аргумента в дополнение к аргументу ILogger :

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

В следующем примере показан RegisterTypes метод на платформе Android:

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

В этом примере App.RegisterTypeWithParameters метод регистрирует контейнер PhotoPicker внедрения зависимостей. Метод регистрации гарантирует, что MainActivity экземпляр будет введен в качестве аргумента Context , и что Logger тип будет введен в качестве аргумента ILogger .

Когда пользователь переходит на страницу выбора фотографий и выбирает фотографию, OnSelectPhotoButtonClicked обработчик выполняется:

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> При вызове метода метод разрешения зависимостей будет вызываться для разрешения PhotoPicker типа из контейнера внедрения зависимостей, который также разрешает и внедряет Logger тип в PhotoPicker конструктор.

Примечание.

Этот Resolve<T> метод необходимо использовать при разрешении типа из контейнера внедрения зависимостей приложения через .DependencyService