Pass Autofac container to WPF UserControl

2019-05-10 10:00发布

I am using autofac to resolve Views and ViewModels in a WPF application. IComponentContext is being passed into the View automatically.

An example:

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

In the XAML for this view there are UserControls being created that have their own ViewModels. An example:

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

Autofac is not creating the UserControl (the View is) so Autofac cannot inject dependencies into the UserControl's constructor.

How can I get the reference to IComponentContext into a UserControl that is declared in the initial View's XAML?

I feel that I either need Autofac to somehow create my UserControl, that I need to revert to the discouraged Global static container (ick), or I have to use a DependencyProperty to pass the container down (also ick).

2条回答
叛逆
2楼-- · 2019-05-10 10:29

You should not pass componentContext to BusinessAuto view, instead you should pass AdditionalCoveragesControl.

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

XAML:

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

Then all your views will be independent from Autofac, you should just register all of them in container.

查看更多
贪生不怕死
3楼-- · 2019-05-10 10:32

I wouldn't inject (what is effectively) your container, as this is an inferior form of inversion of control called Service Locator, with deficiencies that can be summed up by your current situation: you end up needing to inject the container into EVERYTHING.

Instead, you need to approach the problem from the point of view of "What is this component in charge of creating, and what does it need to do it?"

As mentioned by Lonni-Loki, one option is to inject a fully formed control but I disagree on that point: if the main view has the responsibility of creating this sub component, then it should create it - but to facilitate that responsibility, you should inject the main view with the services/models/etc it would then need to pass along or otherwise use to create it. Autofac's factory method stubs are ideal for this:

For example if the sub view needs an IFooViewModel, you can inject the container with a Func<IFooViewModel< (a factory method registered with the context), which it can then use to "feed on demand" the new contained view.

(or a Func<arg1, arg2, etc, IFooViewModel>, as your needs require)

One handy rule of thumb is when considering class X, first take anywhere you "new up" anything and instead pass that into the constructor. Now look at the code and ask yourself "If I wanted an instance of class X, what would I need to pass the constructor?" Those are your dependencies.

Let's take a more hands-on example...say you've got a structure like this:

  • Application creates MainWindow

  • MainWindow creates SubView1, needs IMainWindowViewModel

  • SubView1 needs ISubView1Model, IFooService

  • SubView1 creates SubView2

  • SubView2 needs ISubView2Model, IBarService

So our ctors would look like:

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

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

public SubView2(
    ISubView2ModelViewModel viewModel, 
    IBarService barService)

When setting up your container, you'd have something like so:

(note, I use a variety of IoC containers, so my Autofac syntax might be rusty)

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);
    };
});

The glorious (and in my opinion, only) use for IoC containers is the "You figure out how to get all the bits based on what I've already told you" part - otherwise, you might as well use manual injection, where you pass in dependencies by hand. That said, our potentially complicated construction of MainWindow is now just:

container.Resolve<MainWindow>();

I hope I didn't make too many typos/errors in the code, but I haven't used Autofac for a while.

查看更多
登录 后发表回答