本文通过 WPF Gallery 这个项目学习依赖注入的相关概念与如何在WPF中进行依赖注入。
依赖注入(Dependency Injection,简称DI)是一种设计模式,用于实现控制反转(Inversion of Control,简称IoC)原则。依赖注入的主要目的是将对象的创建和对象之间的依赖关系的管理从对象内部转移到外部容器或框架中,从而提高代码的可维护性、可测试性和灵活性。
依赖注入的核心概念
依赖注入的类型
构造函数注入:依赖的对象通过类的构造函数传递。
public class OrderService
{
private readonly IProductRepository _productRepository;
public OrderService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
}
属性注入:依赖的对象通过类的公共属性传递。
public class OrderService
{
public IProductRepository ProductRepository { get; set; }
}
方法注入:依赖的对象通过类的方法参数传递。
public class OrderService
{
public void ProcessOrder(IProductRepository productRepository)
{
// 使用 productRepository 处理订单
}
}
依赖注入(Dependency Injection,简称DI)是一种设计模式,通过它可以将对象的创建和对象之间的依赖关系的管理从对象内部转移到外部容器或框架中。进行依赖注入有以下几个重要的原因和优点:
本文通过 WPF Gallery 项目学习在WPF中如何使用依赖注入,代码地址:
https://github.com/microsoft/WPF-Samples/blob/main/SampleApplications/WPFGallery
这个项目中实现依赖注入,使用到了这两个包:
首先查看App.xaml.cs中的内容:
public partial class App : Application
{
private static readonly IHost _host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<MainWindow>();
services.AddSingleton<MainWindowViewModel>();
services.AddTransient<DashboardPage>();
services.AddTransient<DashboardPageViewModel>();
services.AddTransient<ButtonPage>();
services.AddTransient<ButtonPageViewModel>();
services.AddTransient<CheckBoxPage>();
services.AddTransient<CheckBoxPageViewModel>();
services.AddTransient<ComboBoxPage>();
services.AddTransient<ComboBoxPageViewModel>();
services.AddTransient<RadioButtonPage>();
services.AddTransient<RadioButtonPageViewModel>();
services.AddTransient<SliderPage>();
services.AddTransient<SliderPageViewModel>();
services.AddTransient<CalendarPage>();
services.AddTransient<CalendarPageViewModel>();
services.AddTransient<DatePickerPage>();
services.AddTransient<DatePickerPageViewModel>();
services.AddTransient<TabControlPage>();
services.AddTransient<TabControlPageViewModel>();
services.AddTransient<ProgressBarPage>();
services.AddTransient<ProgressBarPageViewModel>();
services.AddTransient<MenuPage>();
services.AddTransient<MenuPageViewModel>();
services.AddTransient<ToolTipPage>();
services.AddTransient<ToolTipPageViewModel>();
services.AddTransient<CanvasPage>();
services.AddTransient<CanvasPageViewModel>();
services.AddTransient<ExpanderPage>();
services.AddTransient<ExpanderPageViewModel>();
services.AddTransient<ImagePage>();
services.AddTransient<ImagePageViewModel>();
services.AddTransient<DataGridPage>();
services.AddTransient<DataGridPageViewModel>();
services.AddTransient<ListBoxPage>();
services.AddTransient<ListBoxPageViewModel>();
services.AddTransient<ListViewPage>();
services.AddTransient<ListViewPageViewModel>();
services.AddTransient<TreeViewPage>();
services.AddTransient<TreeViewPageViewModel>();
services.AddTransient<LabelPage>();
services.AddTransient<LabelPageViewModel>();
services.AddTransient<TextBoxPage>();
services.AddTransient<TextBoxPageViewModel>();
services.AddTransient<TextBlockPage>();
services.AddTransient<TextBlockPageViewModel>();
services.AddTransient<RichTextEditPage>();
services.AddTransient<RichTextEditPageViewModel>();
services.AddTransient<PasswordBoxPage>();
services.AddTransient<PasswordBoxPageViewModel>();
services.AddTransient<ColorsPage>();
services.AddTransient<ColorsPageViewModel>();
services.AddTransient<LayoutPage>();
services.AddTransient<LayoutPageViewModel>();
services.AddTransient<AllSamplesPage>();
services.AddTransient<AllSamplesPageViewModel>();
services.AddTransient<BasicInputPage>();
services.AddTransient<BasicInputPageViewModel>();
services.AddTransient<CollectionsPage>();
services.AddTransient<CollectionsPageViewModel>();
services.AddTransient<MediaPage>();
services.AddTransient<MediaPageViewModel>();
services.AddTransient<NavigationPage>();
services.AddTransient<NavigationPageViewModel>();
services.AddTransient<TextPage>();
services.AddTransient<TextPageViewModel>();
services.AddTransient<DateAndTimePage>();
services.AddTransient<DateAndTimePageViewModel>();
services.AddTransient<StatusAndInfoPage>();
services.AddTransient<StatusAndInfoPageViewModel>();
services.AddTransient<SamplesPage>();
services.AddTransient<SamplesPageViewModel>();
services.AddTransient<DesignGuidancePage>();
services.AddTransient<DesignGuidancePageViewModel>();
services.AddTransient<UserDashboardPage>();
services.AddTransient<UserDashboardPageViewModel>();
services.AddTransient<TypographyPage>();
services.AddTransient<TypographyPageViewModel>();
services.AddSingleton<IconsPage>();
services.AddSingleton<IconsPageViewModel>();
services.AddSingleton<SettingsPage>();
services.AddSingleton<SettingsPageViewModel>();
services.AddSingleton<AboutPage>();
services.AddSingleton<AboutPageViewModel>();
}).Build();
[STAThread]
public static void Main()
{
_host.Start();
App app = new();
app.InitializeComponent();
app.MainWindow = _host.Services.GetRequiredService<MainWindow>();
app.MainWindow.Visibility = Visibility.Visible;
app.Run();
}
}
IHost是什么?
在C#中,IHost
是一个接口,它是.NET 中用于构建和配置应用程序的Host
的概念的抽象。IHost
接口定义了启动、运行和管理应用程序所需的服务和组件的集合。它通常用于ASP.NET Core应用程序,但也适用于其他类型的.NET 应用程序,如控制台应用程序或WPF程序。
IHost
接口由HostBuilder
类实现,它提供了创建和配置IHost
实例的方法。HostBuilder
允许你添加各种服务,如日志记录、配置、依赖注入容器等,并配置应用程序的启动和停止行为。
提供了用于使用预配置默认值创建Microsoft.Extensions.Hosting.IHostBuilder实例的方便方法。
返回一个IHostBuilder。
向容器中添加服务。此操作可以调用多次,其结果是累加的。
参数configureDelegate的含义是配置Microsoft.Extensions.DependencyInjection.IServiceCollection的委托,
该集合将用于构造System.IServiceProvider。
该委托需要两个参数类型分别为HostBuilderContext、IServiceCollection没有返回值。
这里传入了一个满足该委托类型的Lambda表达式。
在C#中,() => {}
是一种Lambda表达式的语法。Lambda表达式是一种轻量级的委托包装器,它可以让你定义一个匿名方法,并将其作为参数传递给支持委托或表达式树的方法。
Lambda表达式提供了一种简洁的方式来定义方法,特别是在需要将方法作为参数传递给其他方法时,它们非常有用。
在添加服务,这里出现了两种生命周期,除了AddSingleton、AddTransient外还有AddScoped。
这些方法定义了服务的生命周期,即服务实例在应用程序中的创建和管理方式。
AddSingleton
AddTransient
AddScoped
使用这些服务
在Main函数中:
启动_host
,通过_host.Services.GetRequiredService<MainWindow>();
获取MainWindow实例。
以MainWindow类为例,查看MainWindow.xaml.cs中MainWindow的构造函数:
public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
_serviceProvider = serviceProvider;
ViewModel = viewModel;
DataContext = this;
InitializeComponent();
Toggle_TitleButtonVisibility();
_navigationService = navigationService;
_navigationService.Navigating += OnNavigating;
_navigationService.SetFrame(this.RootContentFrame);
_navigationService.Navigate(typeof(DashboardPage));
WindowChrome.SetWindowChrome(
this,
new WindowChrome
{
CaptionHeight = 50,
CornerRadius = default,
GlassFrameThickness = new Thickness(-1),
ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
UseAeroCaptionButtons = true
}
);
this.StateChanged += MainWindow_StateChanged;
}
去掉与本主题无关的内容之后,如下所示:
public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
_serviceProvider = serviceProvider;
ViewModel = viewModel;
_navigationService = navigationService;
}
有没有发现不用自己new这些对象了,这些对象的创建由依赖注入容器来管理,在需要这些对象的时候,像现在这样通过构造函数中注入即可。
如果没有用依赖注入,可能就是这样子的:
public MainWindow()
{
_serviceProvider = new IServiceProvider();
ViewModel = new MainWindowViewModel();
_navigationService = new INavigationService();
}
本文先介绍依赖注入的概念,再解释为什么要进行依赖注入,最后通过 WPF Gallery 这个项目学习如何在WPF中使用依赖注入。
1、[WPF-Samples/Sample Applications/WPFGallery at main · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Sample Applications/WPFGallery)