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 作为依赖项注入容器,该容器提供 IContainerContainerBuilder 类型。 可以同样使用替代依赖项注入容器,但会使用与此处所示不同的 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从应用程序的依赖项注入容器解析类型时,必须使用 方法。