CommunityToolkit.Mvvm中的Ioc
什么是Ioc
在软件工程中,控制反转(IoC)是一种设计原则,其中计算机程序的自定义编写部分从外部源(例如框架)接收控制流。术语“反转”是历史性的:与过程式编程相比,具有这种设计的软件架构“反转”了控制。在过程式编程中,程序的自定义代码调用可重用库来处理通用任务,但在控制反转的情况下,是外部源或框架调用自定义代码。
在传统编程中,业务逻辑的流程是由静态绑定到彼此的对象决定的。
以赋值ViewModel为例,例如我有一个MainWindowViewModel,需要绑定到MainWindow的DataContext上
1 public MainWindow() 2 { 3 this.InitializeComponent(); 4 5 MainWindowViewModel viewmodel = new MainWindowViewModel(); 6 this.DataContext = viewmodel ; 7 }
通过反转控制,流程取决于程序执行过程中建立的对象图(https://www.cnblogs.com/zhaotianff/p/17402238.html)。这种运行时绑定是通过依赖注入(Dependency Inject)或服务定位器(Service Locator)等机制实现的。
可以看到下面的代码,我们先构造一个ServiceProvider
///构建ServiceProvider
1 private static IServiceProvider ConfigureServices() 2 { 3 var services = new ServiceCollection(); 4 5 //使用Dependency Inject 6 //注入ViewModel 7 services.AddTransient<MainWindowViewModel>(); 8 9 return services.BuildServiceProvider(); 10 }
此时我们可以通过ServiceLocator来获取ViewModel
1 public MainWindow() 2 { 3 this.InitializeComponent(); 4 this.DataContext = App.Current.Services.GetService<MainWindowViewModel>(); 5 }
CommunityToolkit.MVVM中的Ioc
在WPF MVVM模式中发时,使程序模块化,常用的一种形式就是Ioc(控件反转)。比较常见的就是是使用依赖注入(Dependency Inject, DI),它包括创建注入后台类的服务(即作为参数传递给ViewModel构造函数)——这使得使用这些服务的代码不依赖于这些服务的实现细节。在需要的地方注入,然后在后端代码可以使用这些功能。
CommunityToolkit.MVVM包不提供内置的Ioc框架,需要引用:Microsoft.Extensions.DependencyInjection包,它提供了一组功能齐全、功能强大的DI API,并作为一个易于设置和使用的IServiceProvider。
在后面的文章中,将会介绍这个包的使用方法。
Ioc和DI的区别
Ioc是一种设计原则 ,是一种思想 。
DI是一种实现方式,它可以注入后台类的服务到一个容器中,在需要使用的时候再取出来。也可以帮助我们将参数传递给构造函数。
在前面的示例代码中,我们注入了一个ViewModel
1 services.AddTransient<MainWindowViewModel>();
然后在需要使用的时候,再取出来
1 this.DataContext = App.Current.Services.GetService<MainWindowViewModel>();
如果我们需要将参数传递给构造函数,可以看到下面的示例代码:
以一个计算器工具为例
我们创建了一个CalculatorService类,用于实现计算器的各个逻辑。
1 public class CalculatorService 2 { 3 public int Add(int a,int b) 4 { 5 return a + b; 6 } 7 8 public int Sub(int a,int b) 9 { 10 return a - b; 11 } 12 }
构建ServiceProvider
1 var services = new ServiceCollection(); 2 3 //注入CalculatorService 4 services.AddScoped<CalculatorService>(); 5 6 //注入MainWindowViewModel 7 services.AddTransient<MainWindowViewModel>(); 8 9 return services.BuildServiceProvider();
然后将CalculatorService通过构造函数传入MainWindowViewModel
1 public class MainWindowViewModel : ObservableObject 2 { 3 private CalculatorService calculatorService; 4 5 public MainWindowViewModel(CalculatorService calculatorService) 6 { 8 this.calculatorService = calculatorService; 9 } 10 11 }
这样在创建ViewModel时,ServiceProvider会自动将CalculatorService注入
注意:需要从ServiceProvider去取,而不是去手动去创建,这样就违背了Ioc的原则。
1 public partial class MainWindow : Window 2 { 3 public MainWindow() 4 { 5 InitializeComponent(); 6 7 this.DataContext = App.Current.Services.GetService<MainWindowViewModel>(); 8 } 9 }
在CommunityToolkit.MVVM实现Ioc的步骤
1、nuget引用 Microsoft.Extensions.DependencyInject包
2、配置和解析服务
1 public sealed partial class App : Application 2 { 3 public App() 4 { 5 Services = ConfigureServices(); 6 7 this.InitializeComponent(); 8 } 9 10 /// <summary> 11 /// 获取当前App的实例 12 /// </summary> 13 public new static App Current => (App)Application.Current; 14 15 /// <summary> 16 /// 获取 IServiceProvider实例来解析应用程序的Services. 17 /// </summary> 18 public IServiceProvider Services { get; } 19 20 /// <summary> 21 /// 配置应用程序的Services 22 /// </summary> 23 private static IServiceProvider ConfigureServices() 24 { 25 var services = new ServiceCollection(); 26 27 services.AddSingleton<IFilesService, FilesService>(); 28 services.AddSingleton<ISettingsService, SettingsService>(); 29 services.AddSingleton<IClipboardService, ClipboardService>(); 30 services.AddSingleton<IShareService, ShareService>(); 31 services.AddSingleton<IEmailService, EmailService>(); 32 33 return services.BuildServiceProvider(); 34 } 35 }
不同注入类型的区别:
AddTransient: 每次service请求都是获得不同的实例.
AddScoped: 对于同一个请求返回同一个实例,不同的请求返回不同的实例.
AddSingleton: 每次都是获得同一个实例, 单一实例模式.
3、获取Service的实例
通过下面的方式获取FilesService的实例,然后就可以使用FilesService提供的功能了。
1 IFilesService filesService = App.Current.Services.GetService<IFilesService>();
4、构造函数注入
这里和前面介绍Ioc和DI区别时已经介绍过了
DI ServiceProvider在创建所请求类型的实例时能够自动解析注册服务之间的间接依赖关系。
1 public class FileLogger : IFileLogger 2 { 3 private readonly IFilesService FileService; 4 private readonly IConsoleService ConsoleService; 5 6 public FileLogger( 7 IFilesService fileService, 8 IConsoleService consoleService) 9 { 10 FileService = fileService; 11 ConsoleService = consoleService; 12 } 13 14 // Methods for the IFileLogger interface here... 15 }
5、注入ViewModel
1 /// <summary> 2 /// 配置Services 3 /// </summary> 4 private static IServiceProvider ConfigureServices() 5 { 6 var services = new ServiceCollection(); 7 8 // Services 9 services.AddSingleton<IContactsService, ContactsService>(); 10 services.AddSingleton<IPhoneService, PhoneService>(); 11 12 // Viewmodels 13 services.AddTransient<ContactsViewModel>(); 14 15 return services.BuildServiceProvider(); 16 }
1 public ContactsView() 2 { 3 this.InitializeComponent(); 4 this.DataContext = App.Current.Services.GetService<ContactsViewModel>(); 5 }
ServiceLocator
我们可以构造一个ServiceLocator方便我们将ViewModel绑定到DataContext
1、添加ServiceLocator类
1 public class ViewModelLocator 2 { 3 public MainWindowViewModel Main 4 { 5 get 6 { 7 return App.Current.Services.GetService<MainWindowViewModel>(); 8 9 //or 10 //return Ioc.Default.GetService<MainWindowViewModel>(); 11 } 12 } 13 14 }
2、在App.xaml中创建ViewModelLocator实例
1 <Application.Resources> 2 <vm:ViewModelLocator xmlns:vm="clr-namespace:CommunityToolkitMVVMIocDemo_MVVM.ViewModels" 3 x:Key="Locator" /> 4 </Application.Resources>
3、绑定DataContext
1 <Window x:Class="CommunityToolkitMVVMIocDemo_MVVM.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:CommunityToolkitMVVMIocDemo_MVVM" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="450" Width="800" DataContext="{Binding Source={StaticResource Locator}, Path=Main}"> 9 </Window>
参考资料
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/ioc