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

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. No comments yet.
  1. 09/01/2020 at 07:00
  2. 12/01/2020 at 11:52

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: