Ioc(控制反转

使用 MVVM 模式提高应用程序代码库中的模块化程度的最常用模式是使用某种形式的反转控制。 其中有一种最常见的解决方案使用依赖关系注入,该解决方案存在于创建多个注入后端类的服务(即以参数的形式传递给 viewmodel 构造函数)的过程中,这允许使用这些服务的代码不依赖这些服务的实现详细信息,并且也可以轻松地交换这些服务的具体实现。 这种模式还可以通过服务将特定于平台的功能抽象出来,然后在需要的地方注入这些功能,从而使后端代码可以轻松使用这些功能。

MVVM 工具包并没有提供内置的 API 来促进这种模式的使用,因为已存在专门用于此的专用库(如 Microsoft.Extensions.DependencyInjection 包),它提供了功能齐全、强大的 DI API 集,并充当了易于设置和使用的 IServiceProvider。 以下指南将参考此库,并提供有关如何使用 MVVM 模式将其集成到应用程序中的一系列示例。

平台 API:Ioc

配置和解析服务

第一步是声明 IServiceProvider 实例,并在启动时初始化所有必要的服务。 例如,在 UWP 上(但类似的设置也可以在其他框架上使用):

public sealed partial class App : Application
{
    public App()
    {
        Services = ConfigureServices();

        this.InitializeComponent();
    }

    /// <summary>
    /// Gets the current <see cref="App"/> instance in use
    /// </summary>
    public new static App Current => (App)Application.Current;

    /// <summary>
    /// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
    /// </summary>
    public IServiceProvider Services { get; }

    /// <summary>
    /// Configures the services for the application.
    /// </summary>
    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();

        services.AddSingleton<IFilesService, FilesService>();
        services.AddSingleton<ISettingsService, SettingsService>();
        services.AddSingleton<IClipboardService, ClipboardService>();
        services.AddSingleton<IShareService, ShareService>();
        services.AddSingleton<IEmailService, EmailService>();

        return services.BuildServiceProvider();
    }
}

其中,Services 属性在启动时初始化,所有应用程序服务和 viewmodel 都已注册。 还有一个新的 Current 属性可用于从应用程序的其他视图中轻松访问 Services 属性。 例如:

IFilesService filesService = App.Current.Services.GetService<IFilesService>();

// Use the files service here...

这里最关键的一点是,每个服务很可能都在使用特定于平台的 API,但由于这些 API 都通过我们的代码所使用的接口进行了抽象,因此只要我们只是解析实例并使用它来执行操作,就不需要担心这些 API。

构造函数注入

“构造函数注入”是可用的一项强大功能,这意味着 DI 服务提供商能够在创建所请求类型的实例时自动解析已注册服务之间的间接依赖关系。 请考虑下面的服务:

public class FileLogger : IFileLogger
{
    private readonly IFilesService FileService;
    private readonly IConsoleService ConsoleService;

    public FileLogger(
        IFilesService fileService,
        IConsoleService consoleService)
    {
        FileService = fileService;
        ConsoleService = consoleService;
    }

    // Methods for the IFileLogger interface here...
}

在这里,我们有一个 FileLogger 类型,该类型实现 IFileLogger 接口,并且需要 IFilesServiceIConsoleService 实例。 构造函数注入意味着 DI 服务提供商会自动收集所有必要的服务,如下所示:

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    services.AddSingleton<IFilesService, FilesService>();
    services.AddSingleton<IConsoleService, ConsoleService>();
    services.AddSingleton<IFileLogger, FileLogger>();

    return services.BuildServiceProvider();
}

// Retrieve a logger service with constructor injection
IFileLogger fileLogger = App.Current.Services.GetService<IFileLogger>();

DI 服务提供商将自动检查是否注册了所有必要的服务,然后它将检索它们并调用已注册的 IFileLogger 具体类型的构造函数,以获取要返回的实例。

viewmodel 怎么样?

服务提供商的名称中包含“service”,但它实际上可用于解析任何类的实例,包括 viewmodel! 上述相同的概念仍然适用,包括构造函数注入。 假设我们有一个 ContactsViewModel 类型,通过它的构造函数使用 IContactsServiceIPhoneService 实例。 我们可以采用 ConfigureServices 方法,如下所示:

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    // Services
    services.AddSingleton<IContactsService, ContactsService>();
    services.AddSingleton<IPhoneService, PhoneService>();

    // Viewmodels
    services.AddTransient<ContactsViewModel>();

    return services.BuildServiceProvider();
}

然后在我们的 ContactsView 中,我们将按如下所示分配数据上下文:

public ContactsView()
{
    this.InitializeComponent();
    this.DataContext = App.Current.Services.GetService<ContactsViewModel>();
}

更多文档

有关 Microsoft.Extensions.DependencyInjection 的详细信息,请参阅此处

示例

  • 查看示例应用(适用于多个 UI 框架),以了解 MVVM 工具包的实际运行情况。
  • 还可以在单元测试中查找更多示例。