3

我正在使用 autofac 来解析 WPF 应用程序中的视图和视图模型。IComponentContext 正在自动传递到视图中。

一个例子:

    public BusinessAuto(int proposedCoverageId, IComponentContext componentContext)
    {
        DataContext = componentContext.Resolve<BusinessAutoViewModel>(new TypedParameter(typeof(Int32), proposedCoverageId));
        InitializeComponent();
    }

在此视图的 XAML 中,创建了具有自己的 ViewModel 的 UserControl。一个例子:

<userControl:AdditionalCoveragesControl Margin="0,10"/>

Autofac 没有创建 UserControl(视图是),因此 Autofac 不能将依赖项注入到 UserControl 的构造函数中。

如何获取对 IComponentContext 的引用到在初始视图的 XAML 中声明的 UserControl?

我觉得我要么需要 Autofac 以某种方式创建我的 UserControl,要么我需要恢复到不鼓励的全局静态容器(ick),或者我必须使用 DependencyProperty 来传递容器(也是 ick)。

4

2 回答 2

6

我不会注入(什么是有效的)您的容器,因为这是一种称为服务定位器的控制反转的劣质形式,其缺陷可以通过您当前的情况来总结:您最终需要将容器注入一切。

相反,您需要从“这个组件负责创建什么,它需要做什么?”的角度来处理问题。

正如 Lonni-Loki 所提到的,一种选择是注入一个完全形成的控件,但我不同意这一点:如果主视图有责任创建这个子组件,那么它应该创建它 - 但是为了促进这个责任,你应该用服务/模型/等注入主视图,然后它需要传递或以其他方式用于创建它。Autofac 的工厂方法存根非常适合:

例如,如果子视图需要一个 IFooViewModel,您可以使用 Func<IFooViewModel<(在上下文中注册的工厂方法)注入容器,然后它可以使用它来“按需提供”新包含的视图。

(或 Func<arg1、arg2 等、IFooViewModel>,根据您的需要)

一个方便的经验法则是,在考虑 X 类时,首先将“新”任何东西放在任何地方,然后将其传递给构造函数。现在查看代码并问自己“如果我想要一个 X 类的实例,我需要传递构造函数什么?” 这些是你的依赖。

让我们举一个更实际的例子......假设你有一个这样的结构:

  • 应用程序创建 MainWindow

  • MainWindow创建SubView1,需要IMainWindowViewModel

  • SubView1 需要 ISubView1Model、IFooService

  • SubView1 创建 SubView2

  • SubView2 需要 ISubView2Model、IBarService

所以我们的ctors看起来像:

public MainWindow(IMainWindowViewModel viewModel, 
     Func<SubView1> subView1Factory)

public SubView1(ISubView1Model viewModel,
    IFooService fooService,
    Func<IFooService, SubView2> subView2Factory)

public SubView2(
    ISubView2ModelViewModel viewModel, 
    IBarService barService)

设置容器时,您将拥有如下内容:

(注意,我使用了各种 IoC 容器,所以我的 Autofac 语法可能会生锈)

var builder = new ContainerBuilder();

// Simple bit, register implementations for viewmodel, services
builder.RegisterType<MainWindowViewModel>.As<IMainWindowViewModel>();
builder.RegisterType<SubView1Model>.As<ISubView1Model>();
builder.RegisterInstance<FooService>.As<IFooService>();

// ok, lemme see if I can remember expression syntax...

// Simple case: 'static' resolution of subview
// We want a func that takes no args and returns us a fully-initialized
//  SubView1
builder.Register<Func<SubView1>>(context =>
{
    // Since all the bits of a subview1 are registered, simply
    // resolve it and return
    var view = context.Resolve<SubView1>();
    return () => view;
});

// Complicated case - lets say this viewmodel depends
// on foo service, which it uses to determine which 
// bar service to use
builder.Register<Func<IFooService, SubView2>>(context =>
{
    // and our view model
    var vm = context.Resolve<ISubView2ViewModel>();

    return (service) =>
    {
        var barService = new BarService(service);
        return new SubView2(vm, barService);
    };
});

IoC 容器的光荣(在我看来,唯一的)用途是“您根据我已经告诉过您的内容弄清楚如何获取所有位”部分 - 否则,您不妨使用手动注入,在那里您手动传递依赖项。也就是说,我们对 MainWindow 的潜在复杂构造现在只是:

container.Resolve<MainWindow>();

我希望我没有在代码中犯太多错别字/错误,但我有一段时间没有使用 Autofac。

于 2013-01-05T09:37:54.850 回答
2

您不应将 componentContext 传递给 BusinessAuto 视图,而应传递 AdditionalCoveragesControl。

public BusinessAuto(int proposedCoverageId, BusinessAutoViewModel vm, AdditionalCoveragesControl view)
{ 
  ...
  DataContext = vm;
  InternalView = view;
}    

XAML:

 <ContentPresenter Content="{Binding InternalView}" Margin="0,10"/>

然后你的所有视图都将独立于 Autofac,你应该将它们全部注册到容器中。

于 2013-01-05T09:21:49.810 回答