18

I can't seem to find an solution to this problem. I've seen several questions about this, but none really give me a solution. I am totally new to Autofac and haven't really done much WPF + MVVM, but know the basics.

I have a WPF application (using ModernUI for WPF) which I'm trying to add Autofac to, and I am having a hard time figuring out how to resolve my services within all the views, since they have no access to my container. I have a main view, which is my entry point, where I set up my container:

public partial class MainWindow : ModernWindow
{
    IContainer AppContainer;

    public MainWindow()
    {

        SetUpContainer();

        this.DataContext = new MainWindowViewModel();
        InitializeComponent();

        Application.Current.MainWindow = this; 
    }

    private void SetUpContainer()
    {
        var builder = new ContainerBuilder();

        BuildupContainer(builder);

        var container = builder.Build();

        AppContainer = container;
    }

    private void BuildupContainer(ContainerBuilder builder)
    {
        builder.RegisterType<Logger>().As<ILogger>();
        ...
    }
}

The problem I'm having is figuring out how I can resolve my logger and other services within my other views, where I inject all my dependencies through the ViewModel constructor, like so:

public partial class ItemsView : UserControl
{
    private ItemsViewModel _vm;

    public ItemsView()
    {
        InitializeComponent();

        IFileHashHelper fileHashHelper = new MD5FileHashHelper();
        ILibraryLoader libraryLoader = new LibraryLoader(fileHashHelper);
        ILogger logger = new Logger();

        _vm = new ItemsViewModel(libraryLoader, logger);
        this.DataContext = _vm;
    }
}

Some views have a ridiculous amount of injected parameters, and this is where I want Autofac to come in and help me clean things up.

I was thinking of passing the container to the ViewModel and storing it as a property on my ViewModelBase class, but I've read that this would be an anti-pattern, and even then I don't know if that would automatically resolve my objects within the other ViewModels.

I managed to put together a simple Console Application using Autofac

class Program
{
    static void Main(string[] args)
    {

        var builder = new ContainerBuilder();
        builder.RegisterType<Cleaner>().As<ICleaner>();
        builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();

        var container = builder.Build();

        using (var scope = container.BeginLifetimeScope())
        {

            ICleaner cleaner = container.Resolve<ICleaner>();
            cleaner.Update(stream);
        }
    }
}

but that was simple since it has a single entry point.

I'd like some ideas on how to add Autofac to my WPF app. I'm sure that I'm doing something wrong. Your help is appreciated.

Ben
  • 1,169
  • 3
  • 17
  • 27
  • I use Autofac for all my DI, here is an example I have with WPF\MVVM https://github.com/oriches/Simple.Wpf.DataGrid – AwkwardCoder May 01 '17 at 12:26

3 Answers3

17

Expanding on my comment above:

I use Autofac with all my WPF MVVM applications, I believe it to be one of the better DI frameworks - this is my opinion, but I think it is valid.

Also for me PRISM should be avoided 99% of the time, it's a 'solution looking for a problem' and since most people don't build dynamically composable runtime solutions in WPF it is not needed, i'm sure people would\will disagree.

Like any architectural patterns there is a setup\configuration phase to the application life-cycle, put simply in your case before the first View (window) is shown there will be a whole of setup done for Dependency Injection, Logging, Exception Handling, Dispatcher thread management, Themes etc.

I have several examples of using Autofac with WPF\MVVM, a couple are listed below, I would say look at the Simple.Wpf.Exceptions example:

https://github.com/oriches/Simple.Wpf.Exceptions

https://github.com/oriches/Simple.Wpf.DataGrid

https://github.com/oriches/Simple.MahApps.Template

AwkwardCoder
  • 24,893
  • 27
  • 82
  • 152
  • this might be exactly what I'm looking for. From what I can see, you use the App OnStartup as your entry point and build your container from there. That seems easily doable for me. What I don't see clearly is how do you set up your scope from there? Where is your scope handled? I am referring to your Simple.Wpf.Exceptions project. – Ben May 01 '17 at 14:17
  • Yes, everything starts in the OnStartup, setup exception handling, start up the bootstrapper (DI) and a lot of debug\diagnostic stuff – AwkwardCoder May 01 '17 at 14:23
  • What do you mean by scope? – AwkwardCoder May 01 '17 at 14:23
  • If you mean scope as in scope DI container,in general I don't use them, I have previously along time ago, but now I find if I'm diligent with the architecture and implementation (MVVM) then I don't in general need them. Having said that I did produce an example of how I might have scoped containers in an MVVM application, it's not been updated for along time so the nuget references are rather old - https://github.com/oriches/Simple.Wpf.Composition – AwkwardCoder May 01 '17 at 14:51
  • Yeah that's what I meant. I'll try these tonight and see how they work. I noticed in your Composition project how you control your scope in the App Start and Stop. That seems easier for me to follow. I'll try that one first. – Ben May 02 '17 at 13:29
  • 1
    I was thinking why I haven't recently want to be able to created scoped containers when doing WPF\MVVM, and the conclusion I came to was - if all the services in the application are registered as singletons because they are stateless and only the ViewModels are registered per-instance then I shouldn't need a scope because the services will be shared and the ViewModels shouldl be disposed of in a timely manner – AwkwardCoder May 02 '17 at 14:32
11

You can use a similar technique as your console application:

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<Cleaner>().As<ICleaner>();
        builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();

        // Add the MainWindowclass and later resolve
        build.RegisterType<MainWindow>().AsSelf();

        var container = builder.Build();

        using (var scope = container.BeginLifetimeScope())
        {
            var main = scope.Resolve<MainWindow>();
            main.ShowDialog();
        }
    }
}

Be sure to mark Main with [STAThread]. Then in the project's properties, under the Application tab, set the Startup object to the Program class.

However, I am not certain of the implications of not running App.Run() and of running MainWindow.ShowDialog() instead.

To do the same using App.Run(), do the following:

1) delete StartupUri="MainWindow.xaml" from App.xaml

2) Add the following to App.xaml.cs

protected override void OnStartup(StartupEventArgs e)
{
    var builder = new ContainerBuilder();
    builder.RegisterType<Cleaner>().As<ICleaner>();
    builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();

    // Add the MainWindowclass and later resolve
    build.RegisterType<MainWindow>().AsSelf();

    var container = builder.Build();

    using (var scope = container.BeginLifetimeScope())
    {
        var window = scope.Resolve<MainWindow>();
        window.Show();
    }
}
Swoogan
  • 5,298
  • 5
  • 35
  • 47
  • I guess you would assign the `container` to the `MainWindow` so that it'd be able to resolve more services or other windows if necessary? – t3chb0t Dec 05 '21 at 12:56
  • 1
    @t3chb0t If you need other services in `MainWindow`, you would inject them in the constructor. – Swoogan Dec 06 '21 at 13:36
4

WPF doesn't have a natural composition root or easy DI integration. Prism is a pretty common set of libraries specifically intended to bridge that for you.

(That's not Autofac specific - it's general guidance for adding DI to WPF apps.)

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • I've read about Prism and have also read that it is quite an undertaking to apply it to an existing application (basically rewrite it), so I don't think this fits as my solution. I have a working application and it is fully DI, with unit tests, but I was hoping to use something like Autofac to clean up the injected paramaters. – Ben Apr 29 '17 at 17:51
  • Prism looks great thanks. @Ben - you might be surprised at how achievable the "rewrite" changes are if you already have DI and tests running. Working on a tested codebase in a way where you don't have to write new tests (refactoring) is much much faster than you might expect. It's a tragedy that people forget the "refactor" part of TDD red-green-refactor - it's also the fun part and the end results are a joy to maintain. Difficult if your team members aren't willing to learn frameworks though (so not practical in many contexts). – Seth Nov 26 '18 at 21:58