Разрешение зависимостей в 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