user842818
user842818

Reputation: 335

Dependency Injection in .NET Core 3.0 for WPF

I’m quite familiar with ASP.NET Core and the support for dependency injection out of the box. Controllers can require dependencies by adding a parameter in their constructor. How can dependencies be achieved in WPF UserControls? I tried adding a parameter to the constructor, but that didn’t work. I love the IOC concept and would prefer to bring this forward to WPF.

Upvotes: 21

Views: 23339

Answers (6)

You could do something like this

public partial class App : Application
{
    public static IHost? AppHost { get; private set; }

    public App()
    {
        AppHost = Host.CreateDefaultBuilder()
            .ConfigureServices((hostContext, services) => 
            {
                services.AddSingleton<IDatabaseHelper>(new SQLiteDatabaseHelper(Path.Combine(Environment.CurrentDirectory, "noted.db3")));
                services.AddSingleton<LoginWindow>();
                services.AddSingleton<RegisterWindow>();
                services.AddSingleton<NotesWindow>();
            })
            .Build();
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await AppHost!.StartAsync();
        var startupForm = AppHost.Services.GetRequiredService<LoginWindow>();
        startupForm.Show();
        //base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        await AppHost!.StopAsync();
        base.OnExit(e);
    }
}

You should override the "OnStartup" and "OnExit" methods from App class (that inheritates from Application), be sure to delete the StartupUri property from the xaml file.

In the startup method you should wait for the AppHost being initialized, then get the service of your main window and show it (don't execute the base Startup method, you won't have your dependencies injected)

You will need the Microsoft.Extensions.DependencyInjection and the Microsoft.Extensions.Hosting packages from Nuget.

If you need to use a dependecy in a window only do something like this

public LoginWindow(IDatabaseHelper databaseHelper, RegisterWindow registerWindow, NotesWindow notesWindow)
{
    InitializeComponent();
    DataContext = new LoginVM(databaseHelper);
    btnRegister.Click += (s,e) => registerWindow.ShowDialog();
    (DataContext as LoginVM).OnRequestLogin += (s, e) =>
    {
        MessageBox.Show($"Bienvenido {e.Fullname}");
        notesWindow.Show();
        this.Close();
    };
}

Upvotes: 0

Maytham Fahmi
Maytham Fahmi

Reputation: 33397

I have recently come across this requirement for my project and I solved it this way.

For Dependency Injection in .NET Core 3.0 for WPF. After you create a WPF Core 3 project in your solution, you need to install/add NuGet packages:

Microsoft.Extensions.DependencyInjection

In my case, I created a class called LogBase that I want to use for logging, so in your App class, add the following (and ya this is just a basic example):

private readonly ServiceProvider _serviceProvider;

public App()
{
    var serviceCollection = new ServiceCollection();
    ConfigureServices(serviceCollection);
    _serviceProvider = serviceCollection.BuildServiceProvider();
}
    
private void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ILogBase>(new LogBase(new FileInfo($@"C:\temp\log.txt")));
    services.AddSingleton<MainWindow>();
}
    
private void OnStartup(object sender, StartupEventArgs e)
{
    var mainWindow = _serviceProvider.GetService<MainWindow>();
    mainWindow.Show();
}

In your App.xaml, add Startup="OnStartup" so it looks like this:

<Application x:Class="VaultDataStore.Wpf.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:VaultDataStore.Wpf"
             Startup="OnStartup">
    <Application.Resources>
        
    </Application.Resources>
</Application>

So in your MainWindow.xaml.cs, you inject ILogBase in the constructor like this:

private readonly ILogBase _log;

public MainWindow(ILogBase log)
{
    _log = log;

    ...etc.. you can use _log over all in this class

In my LogBase class, I use any logger I like in my way.

I have added all this together in this GitHub repo.


Meanwhile, I have been asked how to use injection inside user control. I come up with this solution if someone gets the benefit of it. Check it here.

Upvotes: 46

Shenron
Shenron

Reputation: 453

maytham-ɯɐɥʇʎɐɯ answer to configure Dependency Injection from App class and Startup method is good except I implement IServiceProvider as a property

        public IServiceProvider ServiceProvider { get; private set; }

The next step is "how to inject viewmodels" ?

I don't like the viewmodel injection in the window constructor (in code behind) My cleaner solution was to create a view model provider to use in xaml To do this, you can implement it as a MarkupExtension

public class ViewModelProvider : MarkupExtension
{
    #region ctor
    public ViewModelProvider()
    {
    }

    public ViewModelProvider(Type viewModelType)
    {
        ViewModelType = viewModelType;
    }
    #endregion ctor

    #region properties
    public Type ViewModelType { get; set; }
    #endregion properties

    #region methods
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return ((App)Application.Current).ServiceProvider.GetService(ViewModelType);
    }
    #endregion methods
}

Then in xaml, you can ask the viewmodel type you want to ViewModelProvider

DataContext="{local:ViewModelProvider local:MainViewModel}"

Upvotes: 4

Mark Feldman
Mark Feldman

Reputation: 16119

Generally speaking, you don't. You use dependency injection in your view models and you then use data binding to bind your views to those instead.

That's not to say it can't be done. MVVM Light, for example, creates an injector class and then declares an instance of it in App.xaml, which is almost the same as declaring a global variable:

<Application.Resources>
    <ResourceDictionary>
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:MyMvvmProject.ViewModel" />
    </ResourceDictionary>
</Application.Resources>

Windows and user controls that are part of the visual tree can bind to application resources, so in that framework, the main window typically binds to the view model like so:

<Window x:Class="MyMvvmProject.MainWindow"
    ...
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}">

...where Main is a property of the locator class:

public MainViewModel Main
{
    get
    {
        return ServiceLocator.Current.GetInstance<MainViewModel>();
    }
}

This is not very good IoC because it puts all of your injectables in a single class. In practice, you would break it up into specialized factories etc at various levels.

But seriously, don't do any of this. Use DI in your view model layer and use loose data-binding to couple it to your view. This will allow you to exploit the full power of dependency injection, partly by de-coupling it from a layer where it's not needed and partly by allowing flexibility to re-configure your dependencies for different environments i.e. web, desktop, mobile and especially unit testing where views don't get created at all.

(Disclaimer: I haven't yet used .NET Core for WPF, so what I've presented here may be a bit .NET-specific, but the general principles remain).

Upvotes: 1

In WPF, you use a pattern called Model-View-ViewModel (MVVM for short). Your dependencies are injected into the view-model (using same IoC frameworks as you use in ASP.NET, e.g. AutoFac) and your views (UserControls) are registered as data templates to your view-models.

In that way, you structure your application around view-models and the views (that depend on the view models) are resolved as if the view-model depended on the view. Views may access their view-model through their DataContext property. So you can use the view-model as a facade to inject whatever to your views.

Upvotes: 2

OptimusPrime
OptimusPrime

Reputation: 61

Good question, one cannot have controls without non parameterized constructor in xaml. If you want, you need to instantiate it from code, but xaml won't call that constructor.

Upvotes: 2

Related Questions