You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不借助Prism导航实现WPF UserControl构造函数注入

问题

我用Prism的Navigation功能时,它能正常把服务注入视图或视图模型的构造函数,实现INavigationAware接口也能接收参数,整体运行没问题。但有时候Prism的松耦合太“过”了,为了让UserControl真正可复用,我经常需要在XAML里直接绑定属性,而不是只靠NavigationParameter传参,同时还想保留Prism默认的构造注入服务的能力。

我想要的控件具备这些特性:

  • 是带DependencyProperties的常规UserControl
  • 能直接在XAML中布局
  • 支持绑定操作
  • 不需要专用视图模型
  • 把自身设为DataContext

这部分实现不难,但怎么实现依赖注入找不到非hack的办法。比如我有个带单个依赖属性的简单Widget控件:

namespace MyApp.Controls;
public partial class Widget: UserControl
{
    Widget()  // 没有任何服务注入
    {
        InitializeComponent();
        Scene.DataContext = this;
    }

    public static readonly DependencyProperty WidgetLabelProperty = 
        DependencyProperty.Register(nameof(WidgetLabel), typeof(string),
            typeof(Widget), new PropertyMetadata(string.Empty));

    public string WidgetLabel
    { 
        get => (string)GetValue(WidgetLabelProperty);
        set => SetValue(WidgetLabelProperty, value);
    }
}

因为WPF会直接构造这个控件,没法像Prism控件那样给构造函数加参数:

Widget(IMyCustomService customService) // **** 这样行不通 ***
{
    InitializeComponent();
    Scene.DataContext = this;
}

所以如果要访问服务,我想到的唯一办法是加个DependencyProperty当“后门”:

public static readonly DependencyProperty CustomServiceProperty = 
    DependencyProperty.Register(nameof(CustomService), typeof(ICustomService),
        typeof(Widget), new PropertyMetadata(null));

public ICustomService? CustomService
{ 
    get => GetValue(CustomServiceProperty) as ICustomService;
    set => SetValue(CustomServiceProperty , value);
}

但这种方式允许用户把这个属性设为null,有风险。有没有更合理的非hack实现方式?

解决方案

方法1:在控件初始化时直接从Prism容器获取服务

Prism的容器是全局可访问的(通过PrismApplicationBase.Current.Container),你可以在控件构造函数或Loaded事件里直接解析所需服务,既不用暴露公共属性,也能通过容器的注册保证服务不为null:

namespace MyApp.Controls;
public partial class Widget: UserControl
{
    private readonly IMyCustomService _customService;

    Widget()
    {
        InitializeComponent();
        Scene.DataContext = this;
        // 从容器解析服务,未注册会直接抛出异常,避免null风险
        _customService = PrismApplicationBase.Current.Container.Resolve<IMyCustomService>();
    }

    // 你的依赖属性代码...
}

这种方式的优势是控件自主获取服务,无需外部传入,且异常能提前暴露未注册的问题,不会出现运行时null的情况。唯一的耦合是控件依赖Prism容器,这在Prism项目中属于可接受的场景。

方法2:通过附加属性实现隐式注入

创建一个附加属性,专门负责给目标控件注入指定服务,同时在回调逻辑里确保服务不为null:

public static class ServiceInjection
{
    public static readonly DependencyProperty InjectCustomServiceProperty =
        DependencyProperty.RegisterAttached(
            "InjectCustomService",
            typeof(bool),
            typeof(ServiceInjection),
            new PropertyMetadata(false, OnInjectCustomServiceChanged));

    public static bool GetInjectCustomService(DependencyObject obj)
    {
        return (bool)obj.GetValue(InjectCustomServiceProperty);
    }

    public static void SetInjectCustomService(DependencyObject obj, bool value)
    {
        obj.SetValue(InjectCustomServiceProperty, value);
    }

    private static void OnInjectCustomServiceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue && d is Widget widget)
        {
            var service = PrismApplicationBase.Current.Container.Resolve<IMyCustomService>();
            // 给控件私有字段赋值,不暴露公共属性
            widget.SetCustomService(service);
        }
    }
}

然后在Widget中添加私有方法接收服务:

private IMyCustomService _customService;

private void SetCustomService(IMyCustomService service)
{
    _customService = service ?? throw new ArgumentNullException(nameof(service));
}

XAML中只需添加附加属性即可完成注入:

<controls:Widget WidgetLabel="测试" 
                 local:ServiceInjection.InjectCustomService="True"/>

这种方式既保持了控件的封装性(服务是私有字段),又避免了用户手动设置服务的可能,完全由附加属性自动完成注入,同时通过异常保证服务不为null。

方法3:自定义控件工厂(适合批量场景)

如果有大量同类控件需要注入服务,可以自定义一个基于Prism容器的控件工厂,通过XAML扩展标记来引用控件实例。这种方式适合复杂场景,能统一管理控件的创建与注入逻辑。


内容的提问来源于stack exchange,提问作者Joe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 14:50:38