Home > .NET Core, C#, MVVM, WPF > Using the MVVM pattern in WPF applications running on .NET Core

Using the MVVM pattern in WPF applications running on .NET Core

07/01/2020

Some time ago we talked about leveraging .NET Core features (like Host Builder, Service Provider and Dependency Injection) in WPF applications. Now it’s time to integrate them with the MVVM pattern. In this article we’ll use MVVM Light, but only for what concerns helpers methods and classes for View Models. In particular, instead of its SimpleIoc, we’ll show how to use the .NET Core ServiceProvider. So, let’s start from the code that we realized in the latest article and modify it to add MVVM support.

First of all, let’s add the MvvmLightLibsStd10 NuGet package to the project. Then, we need to create a ViewModel for our MainWindow. Keeping in mind that in our original sample the MainWindow receives two parameters in the constructor via Dependency Injection, we want to move them to the View Model. So, we’ll have something like this:

public class MainViewModel : ViewModelBase
{
    private readonly ISampleService sampleService;
    private readonly AppSettings settings;

    public MainViewModel(ISampleService sampleService, 
        IOptions<AppSettings> options)
    {
        this.sampleService = sampleService;
        settings = options.Value;
    }
}

Then, we need to modify the App.xaml.cs file to register this View Model. We also need to expose a reference to the ServiceProvider (we’ll use it later):

public partial class App : Application
{
    private readonly IHost host;

    public static IServiceProvider ServiceProvider { get; private set; }

    public App()
    {
        // Original host configuration...

        ServiceProvider = host.Services;
    }

    private void ConfigureServices(IConfiguration configuration, 
        IServiceCollection services)
    {
        services.Configure<AppSettings>(configuration
            .GetSection(nameof(AppSettings)));
        services.AddScoped<ISampleService, SampleService>();

        // Register all ViewModels.
        services.AddSingleton<MainViewModel>();

        // Register all the Windows of the applications.
        services.AddTransient<MainWindow>();
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await host.StartAsync();

        var window = ServiceProvider.GetRequiredService<MainWindow>();
        window.Show();

        base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        // Original code...
    }
}

At line 5 we declare a static reference to the IServiceProvider, that we assign at the end of the App constructor using the IHost.Services property (line 11). In the ConfigureServices method, line 22, we register the MainViewModel. In this way, we can get it from the ServiceProvider and, more important, we can pass to its constructor all the dependencies it needs, that will be automatically resolved during instantiation (in this case, AppSettings and ISampleService from lines 17-19).

Note that, typically, View Models are registered as singleton (line 22), but of course we can decide to use the AddTransient method if necessary for our scenario. Moreover, for the sake of simplicity all the registrations are kept together in the same file, but of course it is possible to organize the code as we want.

Now we need to create the ViewModelLocator, i.e. a class that provides (as the name implies) references to View Models that will be bound to Views:

public class ViewModelLocator
{
    public MainViewModel MainViewModel 
        => App.ServiceProvider.GetRequiredService<MainViewModel>();
}

In this case, we have only one View Model, as at lines 3-4 we get it using the ServiceProvider property that is exposed by the App class. As we said earlier, all registrations are contained in the ConfigureServices method of the App class, so the ViewModelLocator need only to provide getter properties for View Models.

The last thing to do is to reference the ViewModelLocator in XAML and link the View Model to the View. So, in the App.xaml file we need to write something like this:

<Application
    x:Class="WpfNetCoreMvvm.App"
    ...>

    <Application.Resources>
        <ResourceDictionary>
            <vm:ViewModelLocator
                xmlns:vm="clr-namespace:WpfNetCoreMvvm.ViewModels"
                x:Key="Locator" />

            <!-- Application styles definition -->
        </ResourceDictionary>
    </Application.Resources>
</Application>

And, finally, set the DataContext property of MainWindow.xaml:

<Window
    x:Class="WpfNetCoreMvvm.Views.MainWindow"
    ...
    DataContext="{Binding Source={StaticResource Locator}, Path=MainViewModel}">

Now everything is set. We can put a breakpoint in the MainViewModel constructor and verify that it will be correctly invoked when the application starts, with all the dependencies it needs:

The View Model of the .NET Core 3.1 WPF application with dependencies injected

The View Model of the .NET Core 3.1 WPF application with dependencies injected

To make a further test, we can add a bit of code to the View Model:

public class MainViewModel : ViewModelBase
{
    private string input;
    public string Input
    {
        get => input;
        set => Set(ref input, value);
    }

    private readonly ISampleService sampleService;
    private readonly AppSettings settings;

    public RelayCommand ExecuteCommand { get; }

    public MainViewModel(ISampleService sampleService,
        IOptions<AppSettings> options)
    {
        this.sampleService = sampleService;
        settings = options.Value;

        ExecuteCommand = new RelayCommand(async () => await ExecuteAsync());
    }

    private Task ExecuteAsync()
    {
        Debug.WriteLine($"Current value: {input}");
        return Task.CompletedTask;
    }
}

Now we have an Input property (lines 3-8) and a RelayCommand (line 13, with the implementation at lines 24-28). So, as usual, we can use binding in our Window:

<Window
    ...>

    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Text="Input:" />
            <TextBox Width="300" Text="{Binding Input, Mode=TwoWay}" />
            <Button Command="{Binding ExecuteCommand}" Content="Execute" />
        </StackPanel>
    </Grid>
</Window>

Let’s try running the application:

The WPF Application with MVVM running on top of .NET Core

The WPF Application with MVVM running on top of .NET Core

You can download the sample application using the link below:

Using the MVVM pattern in WPF application running on .NET Core

In the next article we’ll add an important feature that is currently missing: the ability to navigate through Window from View Models passing parameters to them upon activation.

Categories: .NET Core, C#, MVVM, WPF
  1. Waldemar
    06/02/2020 at 12:46

    Hi. Good example, but I have one question. In file App.xaml if I remove next strings:

    Then get an exception when run. Is is required? Can we move to some another place this code?

    • 06/02/2020 at 12:55

      I’m sorry, I don’t see in your comment the strings you’re referring to.

      • Waldemar
        06/02/2020 at 13:43

        Download you project from OneDrive =)

      • Waldemar
        06/02/2020 at 13:46

        Ahh… I’m understood… strings is not visible.. strange. From line 17 to line 19, where Style definition.

      • Waldemar
        06/02/2020 at 13:48

        I dont understood why comments no visible… I mean code on lines from 17 to 19 in App.xaml, where Style definition

      • 06/02/2020 at 14:50

        I think it is a sort of bug of .NET Core. Without something in that place, the application crash at startup.

  2. Martin Mc
    11/02/2020 at 15:17

    Many thanks for this Marco. I migrated a WPF project to .net Core 3.1 in December and have not switched fully over from MVVMLight yet (still using CommonServiceLocator). Im going to fully migrate the app now with the help of this superb article. It really is almost identical to a .net core 3.1 Web App, now I can see this. Thanks again.

    • 11/02/2020 at 15:54

      One of the good thing of .NET Core 3.x is that the hosting model is quite the same for every platform. In fact, you can use a similar approach also with Xamarin!

  3. J Ocher
    13/02/2020 at 08:33

    Getting an error on the MainWindow InitializeComponent method.

    “Exception: Cannot find resource named ‘Locator’.”

    For some reason it works in your project, but not in mine even though I’ve copied everything over.

    • 13/02/2020 at 09:42

      There is a strange issue. Try to put anything (like a comment) inside the ResourceDictionary tag in the App.xaml file (take a look to the fourth block of code of the article)

  4. 26/02/2020 at 20:46

    I got the same error.

    App.xaml:

    If you remove style definition, you get an error on the MainWindow initialization:

    Exception: Cannot find resource named ‘Locator’.

    If you take back style definition, the error goes away.
    Do you know why?

    • 27/02/2020 at 09:34

      Unfortunately, it seems to be an issue of .NET Core 3.x. At this moment, this is the only workaround I have found.

  5. Jeff Snell
    28/02/2020 at 20:59

    There seems to be an issue with App.ServiceProvider being used from the XAML Designer.

    If you use a second instance of Visual Studio and debug “WpfSurface.exe” then open MainWindow.xaml you will get an System.ArgumentNullException: ‘Value cannot be null’ on “public MainViewModel MainViewModel => App.ServiceProvider.GetRequiredService();” in the ViewModelLocator

    You might need to set your exceptions to “Break When Thrown” and pick all or just System.ArgumentNullException to force it.

    I believe this is because ServiceProvider in App.xaml.cs is static, and nothing is invoking the App constructor, so “host.Services” is never assigned to ServiceProvider.

    Do you have any advice on a proper fix for this issue? This is essentially preventing the ability to have design time code and a preview from being available.

    • 02/03/2020 at 09:30

      Why do you need to use a second instance of Visual Studio?

      • Jeff Snell
        19/03/2020 at 16:30

        Is there another way to debug XAML Designer issues? That is the only method I know of to debug designer issues and actually capture what is causing the exception.

      • 19/03/2020 at 16:53

        What kind of XAML Designer issues do you have? Are you working with the latest version of Visual Studio 2019?

      • Jeff Snell
        19/03/2020 at 17:13

        Unfortunately with the 16.5 update that just came out the XAML Designer doesn’t work at all for me in applications with certain dependencies. The Windows Event log shows an issue with WpfSurface.exe crashing when looking for .NET Standard libraries that a dependencies for other NuGet packages. I’ve logged it but for now I have to go old school.

        That’s not your problem though of course! Anyway, the issue I was describing was a warning that was being displayed in the XAML Designer. It still displayed fine and worked as a preview, but it just kept showing the warning “Value cannot be null” in the error log.

        Using the method I described originally, that was how I was able to track the warning down to the ViewModelLocator.

        I ‘resolved’ the issue with the following code:

        public MainViewModel MainViewModel {
        get {
        if (ViewModelBase.IsInDesignModeStatic) {
        // MainViewModelMock inherits from MainViewModel and overrides any value that could be displayed in the XAML Designer
        return new MainViewModelMock();
        }

        return App.ServiceProvider.GetRequiredService();
        }
        }

        I guess I was more curious as to other ways of solving it, a better way.

  6. Adam Parker
    03/04/2020 at 23:11

    How can I get the ViewModelLocator to work if I split out my ViewModels to a different project?

    I can’t return App.ServiceProvider.GetRequiredService in the ViewModel project as it would cause a circular reference (Project with App.xaml is already dependent on the ViewModel project).

    • 04/04/2020 at 10:49

      I think you can save the ServiceProvider reference inside a class of the project that contains the view models, so you can use it also in the application project without the circular reference problem.

      • Adam Parker
        04/04/2020 at 17:06

        Thank you. I believe I was able to do exactly what you suggested and it worked.

  1. 09/01/2020 at 07:00
  2. 12/01/2020 at 11:52
Comments are closed.
%d bloggers like this: