如何在WPF中向用户控件注入依赖项

19
什么是在WPF用户控件中透明地注入依赖项(使用IOC容器)的最佳方法?
我假设用户控件是窗口或其他用户控件的XAML的一部分。同时,我认为父元素(无论是谁)不应该负责这个过程。手动从父容器中注入依赖项的解决方案看起来不够简洁。我希望避免显式地管理我的组件的依赖项,因为它违反了IOC的思想。
是否有任何事件在逻辑树创建时被触发,以便我可以拦截它并注入我的依赖项?
编辑: 在此,依赖项也包括ViewModel,Controller和Presenter(无论使用哪种模式)
谢谢, Andrey
7个回答

8

处理WPF中的依赖关系最好的方法是遵循MVVM模式

简而言之,您不直接将依赖项注入到用户控件(视图)中,而是注入到它们的DataContext(视图模型)中。


5
好的,但 ViewModel 只是依赖关系示例。 - andrey.tsykunov
6
你如何将 ViewModel 注入到用户控件中? - andrey.tsykunov
2
从父控件来说,这意味着父控件应该能够为用户控件创建ViewModel(可以通过访问容器或抽象工厂来实现)。另外,我不喜欢父控件负责处理子控件的依赖关系,因为这违反了IoC的原则。无论如何,目前这就是我的做法,但我正在寻找更好的方法。 - andrey.tsykunov
2
ViewModel是我的视图的实现细节,理想情况下,甚至包括控件父级都不应该知道它的存在(包括ViewModel)。我不想每次将控件放入XAML时都要担心初始化control.ViewModel。理想情况下... - andrey.tsykunov
我不是。而且我从未听说过MVVM以某种方式定义了您应该如何管理控件层次结构。很想阅读相关内容。 - andrey.tsykunov
显示剩余6条评论

1

FrameworkElement有一个Initialized事件,您可以挂钩并注入依赖项。您应该测试它是否足够早以适合您的情况。


似乎是在执行视图构造函数时发生的(来自InitializeComponent)。_container.Resolve<MainView>() mainView.Initialized += mainView_Initialized; // 从未发生 mainView.Show(); - andrey.tsykunov

1
我所做的方法是拥有一个总体应用程序类,该类将依赖项注入到您的视图模型类中(假设您正在使用MVVM设计模式?)- 使用像Unity这样的DI容器。请参阅WPF应用程序框架(https://github.com/jbe2277/waf),其中包含您所描述的情况的示例。

我不确定我理解如何从应用程序访问用户控件。无论如何,这就是我想要避免的 - 管理依赖关系。 - andrey.tsykunov
依赖注入可以通过像Unity这样的依赖容器来处理。我不知道你的具体情况,但看起来你没有使用数据绑定(更不用说MVVM了)?如果是这样,我建议你进一步研究这些方面——WPF的很多功能都是因为这些技术而强大的。 - TanvirK
不,我正在使用MVVM,但这并不重要。问题当然是关于使用IOC容器注入依赖项的(无论使用哪个都不重要)。 - andrey.tsykunov

0

现在,随着内置的DI支持,实现这一点变得很容易。

  1. 在您的应用程序中设置服务提供商:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public partial class App
    {
        private static readonly IHost _host = Host
            .CreateDefaultBuilder()
            .ConfigureAppConfiguration(c => { c.SetBasePath(Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!); })
            .ConfigureServices((context, services) =>
            {
                // register your services
                services.AddScoped<ISomething, Something>();
                services.AddScoped<MyUserControlViewModel>();
            })
            .Build();

        /// <summary>
        /// Gets registered service.
        /// </summary>
        /// <typeparam name="T">Type of the service to get.</typeparam>
        /// <returns>Instance of the service or <see langword="null"/>.</returns>
        public static T GetService<T>()
            where T : class
        {
            var service = _host.Services.GetService(typeof(T)) as T;
            return service!;
        }

        /// <summary>
        /// Occurs when the application is loading.
        /// </summary>
        private void OnStartup(object sender, StartupEventArgs e)
        {
            _host.Start();
        }

        /// <summary>
        /// Occurs when the application is closing.
        /// </summary>
        private async void OnExit(object sender, ExitEventArgs e)
        {
            await _host.StopAsync();

            _host.Dispose();
        }
    }

在您的用户控件中,请注意您无法将VM注入到用户控件中,因为在页面中使用用户控件时,将调用无参数构造函数,所以只需从服务提供程序获取它即可。
public partial class MyUserControl : UserControl
{
    public MyUserControlViewModel ViewModel { get; }

    public OrgSwitcherControl()
    {        
        ViewModel = App.GetService<MyUserControlViewModel>();
        DataContext = this;
        InitializeComponent();
    }
}

使用页面,您可以直接注入虚拟机(当然,您还必须在服务集合中注册XAML页面)。

0
我认为唯一可行的解决方案是使用“服务定位器模式”。您的视图模型是可注入的,但控件/窗口本身不是。因此,您可以使用一个IOC容器来管理VM的依赖关系,并将任何业务逻辑依赖项(例如服务)注入到您的视图模型中。
示例
用户控件
public partial class MyControl : UserControl
{

    public MyControl ()
    {
        InitializeComponent();       
        Vm = VmLocator.GetVm<MyControlVm >();
       
    }

    public MyControlVm Vm { get; set; }
}

ViewModel

public class MyControlVm: VmBase{
    private MyDataService  _service;
    public ICommand DoWork => new ActionCommand(OnDoWork)
    MyControlVm(MyDataService service){
      _service=service;
    }
    private async Task OnDoWork(){
        await _service.DoWorkAsync();
    }
}

Bootstrap

   public  static class Bootstrapper {
       public  statvoid Configure(IServiceCollection collection)
        {
    
            collection     
                .AddTransient<MyDataService >()
                .AddTransient<MyOtherDataService>()
        }
   }

通过这个约定,您可以享受依赖注入的好处,但也有一些注意事项。 不幸的是,我认为没有办法控制WPF如何实例化框架元素并覆盖其行为。
希望这对您有所帮助。

-1

我也曾经遇到过这种心理障碍:

我认为父级(无论是谁)都不应该对此负责。

那么谁来负责呢?IoC 的重点在于其他东西(父级、视图模型、某些东西等)定义了依赖关系。


虽然这已经是老话题了,但他正在寻找的是一种简洁的解决方案,以便通过IoC容器触发注入这些依赖项。 - LuckyLikey

-3

解决这个问题的可能方法之一是采用“ViewModel First”方法并使用约定优于配置。


3
这篇帖子如何充分回答问题?使用视图模型优先和为用户控件指定构造函数参数有什么关系? - Dai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接