本文介绍如何将依赖项解析方法注入到 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
类提供使用该方法将依赖项解析方法注入到 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);
在此示例中,依赖项解析方法设置为 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
类型PhotoPicker
,该容器还将解析类型并将其Logger
注入构造函数中。
通过 DependencyService
Resolve<T>
从应用程序的依赖项注入容器解析类型时,必须使用该方法。
使用容器的依赖项解析 (示例)
依赖关系注入
实现视频播放器
从效果调用事件
从图片库中选取照片