Xamarin.Forms 中的依赖项解析
本文介绍如何将依赖项解析方法注入到 中 Xamarin.Forms ,以便应用程序的依赖项注入容器能够控制自定义呈现器、效果和 DependencyService 实现的创建和生存期。 本文中的代码示例取自 使用容器的依赖项解析 示例。
在使用 Model-View-ViewModel (MVVM) 模式的应用程序上下文 Xamarin.Forms 中,依赖项注入容器可用于注册和解析视图模型,以及注册服务并将其注入视图模型。 在视图模型创建期间,容器会注入所需的任何依赖项。 如果尚未创建这些依赖项,容器会先创建并解析这些依赖项。 有关依赖项注入的详细信息,包括将依赖项注入视图模型的示例,请参阅 依赖关系注入。
控制平台项目中类型的创建和生存期通常由 Xamarin.Forms执行,后者使用 Activator.CreateInstance
方法来创建自定义呈现器、效果和 DependencyService
实现的实例。 遗憾的是,这限制了开发人员对这些类型的创建和生存期的控制,以及向其注入依赖项的能力。 可以通过将依赖项解析方法注入来控制 Xamarin.Forms 类型创建方式(通过应用程序的依赖项注入容器或 ) Xamarin.Forms来更改此行为。 但是,请注意,不需要将依赖项解析方法注入 到 Xamarin.Forms中。 Xamarin.Forms 如果未注入依赖项解析方法,将继续创建和管理平台项目中类型的生存期。
注意
虽然本文重点介绍如何使用依赖项注入容器解析 Xamarin.Forms 已注册类型的依赖项解析方法,但也可以注入使用工厂方法解析已注册类型的依赖项解析方法。 有关详细信息,请参阅 使用工厂方法的依赖项解析 示例。
注入依赖项解析方法
类DependencyResolver
提供使用 ResolveUsing
方法将依赖项解析方法注入 到 Xamarin.Forms的功能。 然后,当需要特定类型的实例时 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);
...
}
...
}
在此示例中,依赖项解析方法设置为 lambda 表达式,该表达式使用 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
方法完成的。 此方法在 实例上ContainerBuilder
调用 Autofac 的 Build
方法,这将生成一个新的依赖项注入容器,其中包含已进行的注册。
在后面的部分中, Logger
实现 接口的 ILogger
类注入到类构造函数中。 类 Logger
使用 Debug.WriteLine
方法实现简单的日志记录功能,并用于演示如何将服务注入自定义呈现器、效果和 DependencyService
实现。
注册自定义呈现器
示例应用程序包含一个用于播放 Web 视频的页面,其 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
类使用平台 API 实现 IPhotoPicker
接口。 有关这些依赖项服务的详细信息,请参阅 从图片库中选取照片。
在 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
从应用程序的依赖项注入容器解析类型时,必须使用 方法。