添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

本文介绍如何将依赖项解析方法注入到 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 作为依赖项注入容器,该容器提供 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类型,该容器还将解析类型并将其LoggerVideoPlayerRenderer注入构造函数。

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.RegisterTypeWithParametersVideoPlayerRenderer 注册到依赖项注入容器。 注册方法可确保将 MainActivity 实例作为参数注入 Context ,并将 Logger 类型作为参数注入 ILogger

示例应用程序包含一个页面,该页面使用触摸跟踪效果来拖动 BoxView 页面周围的实例。 该代码 Effect 将添加到 BoxView 以下代码中:

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

TouchEffect类是由RoutingEffect类在每个平台上实现的。该类是一个TouchEffectPlatformEffect类。 平台 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类型,这将解析类型并将其注入LoggerTouchEffect构造函数中。

注册 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.RegisterTypeWithParametersPhotoPicker 注册到依赖项注入容器。 注册方法可确保将 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注入构造函数中。

通过 DependencyServiceResolve<T> 从应用程序的依赖项注入容器解析类型时,必须使用该方法。

  • 使用容器的依赖项解析 (示例)
  • 依赖关系注入
  • 实现视频播放器
  • 从效果调用事件
  • 从图片库中选取照片
  •