如何在作为页面的UserControl中为内部Control实现Dependency Injection(StructureMap)
在WPF里给继承自Control的自定义控件实现依赖注入确实有点棘手——毕竟XAML实例化控件默认只会调用无参构造函数,没法直接用构造注入那套。结合你参考的StructureMap仓库,我给你分享几个可行的方案,你可以根据自己的场景来选:
这是最直接的方式,适合已经在XAML里声明了CustomControl的场景。首先得确保你的DI容器在应用中全局可访问,比如在App类里存一个静态实例:
public partial class App : Application { public static IContainer Container { get; private set; } protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 初始化StructureMap容器,注册你的服务 Container = new Container(cfg => { cfg.For<IMyService>().Use<MyServiceImpl>(); // 其他需要的服务注册... }); // 初始化主窗口等逻辑 } }
然后在你的CustomControl里,借助Loaded事件从容器中获取服务:
public class CustomControl : Control { public IMyService MyService { get; private set; } public CustomControl() { Loaded += CustomControl_Loaded; } private void CustomControl_Loaded(object sender, RoutedEventArgs e) { // 从全局容器获取服务实例 MyService = App.Container.GetInstance<IMyService>(); // 现在就可以用MyService执行业务逻辑了 } }
注意:服务定位器模式虽然简单,但会让控件和DI容器产生耦合,后续单元测试会稍微麻烦些。如果你的控件逻辑复杂,建议考虑后面的方案。
这种方式能避免直接引用全局容器,通过附加属性来完成服务注入,代码更整洁。首先创建一个附加属性类:
public static class DependenciesInjector { public static readonly DependencyProperty InjectServicesProperty = DependencyProperty.RegisterAttached( "InjectServices", typeof(bool), typeof(DependenciesInjector), new PropertyMetadata(false, OnInjectServicesChanged)); public static bool GetInjectServices(DependencyObject obj) { return (bool)obj.GetValue(InjectServicesProperty); } public static void SetInjectServices(DependencyObject obj, bool value) { obj.SetValue(InjectServicesProperty, value); } private static void OnInjectServicesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((bool)e.NewValue && d is CustomControl customControl) { // 从全局容器获取服务并注入到控件 var myService = App.Container.GetInstance<IMyService>(); customControl.MyService = myService; } } }
接着在XAML里给CustomControl设置这个附加属性即可:
<local:CustomControl local:DependenciesInjector.InjectServices="True" />
这样控件加载时,附加属性会自动完成服务注入,和WPF的绑定系统更契合。
如果你的CustomControl不需要在XAML里硬编码,而是可以通过ViewModel控制创建,那优先用这个方案——完全符合DI的构造注入原则。
首先在容器里注册CustomControl:
Container = new Container(cfg => { cfg.For<IMyService>().Use<MyServiceImpl>(); cfg.For<CustomControl>().Use<CustomControl>(); // 注册自定义控件 });
然后在UserControl的ViewModel里,直接注入CustomControl(或者通过容器解析):
public class MyPageViewModel { public CustomControl CustomControlInstance { get; } // 构造注入CustomControl public MyPageViewModel(CustomControl customControl) { CustomControlInstance = customControl; } // 或者通过容器解析(如果不想直接注入控件实例) // public MyPageViewModel(IContainer container) // { // CustomControlInstance = container.GetInstance<CustomControl>(); // } }
最后在UserControl的XAML里,用ContentControl绑定这个实例:
<ContentControl Content="{Binding CustomControlInstance}" />
这种方式让控件的依赖在创建时就被注入,测试起来更方便,也完全遵循MVVM和DI的设计原则。
你参考的仓库用StructureMap注入ViewModel和视图,你可以把CustomControl的注册整合到现有的容器配置里(比如仓库的Bootstrapper类),让整个应用的DI配置保持统一。
另外,如果CustomControl的逻辑复杂,建议把业务逻辑抽离到对应的ViewModel中,控件只负责UI展示和绑定,这样DI的重心可以放在ViewModel上,代码结构会更清晰。
内容的提问来源于stack exchange,提问作者Dominic Boexler

