1
votes

I'm developing my own application to learn something new. It's WPF .net Core 3.1 app using MVVM pattern.

Recently I've decided to include Microsoft DependencyInjection library.

I've removed StartupUri and modifiec app.xaml.cs:

        public IServiceProvider ServiceProvider { get; private set; }
        public IConfiguration Configuration { get; private set; }

        protected override void OnStartup(StartupEventArgs e)
        {
            AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
            InitializeCefSharp();

            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

            Configuration = builder.Build();

            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);

            ServiceProvider = serviceCollection.BuildServiceProvider();

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

        private void ConfigureServices(IServiceCollection services)
        {
            services.Configure<AppSettings>
                (Configuration.GetSection(nameof(AppSettings)));

            // Views
            services.AddSingleton<MainWindow>();
            services.AddSingleton<SettingsView>();
            services.AddSingleton<WwwView>();
            services.AddSingleton<BuildingView>();
            services.AddSingleton<TroopsMovementsView>();

            // ViewModels
            services.AddSingleton<MainWindowViewModel>();
            services.AddSingleton<SettingsViewModel>();
            services.AddSingleton<WwwViewModel>();
            services.AddSingleton<DomainViewModel>();
            services.AddSingleton<WorldViewModel>();
            services.AddSingleton<RegisterViewModel>();
        }

I'm setting DataContext inside Views' constructors. All views except MainWindow are UserControl type.

        private readonly MainWindowViewModel _mainWindowViewModel;

        public MainWindow(MainWindowViewModel mainWindowViewModel)
        {
            _mainWindowViewModel = mainWindowViewModel;

            DataContext = _mainWindowViewModel;

            InitializeComponent();
        }
        private readonly SettingsViewModel _settingsViewModel;

        public SettingsView(SettingsViewModel settingsViewModel)
        {
            _settingsViewModel = settingsViewModel;

            DataContext = settingsViewModel;

            InitializeComponent();
        }

All Views are embedded in MainWindow like this:

        <dz:DockControl Grid.Row="3" Loaded="FrameworkElement_OnLoaded">
            <dz:DockItem x:Name="Settings" TabText="Settings" ShowAction="{dz:ShowAsDockPositionAction DockPosition=RightAutoHide}">
                <views:SettingsView/>
            </dz:DockItem>
            <dz:DockItem x:Name="WWW" TabText="WWW" ShowAction="{dz:ShowAsDockPositionAction DockPosition=Document}" DefaultDockPosition="Document" >
                <views:WwwView/>
            </dz:DockItem>
        </dz:DockControl>

It's visual studio-like docking library.

The problem is that I got exception during startup, that there's no parameterless constructor. But I can not have any parameterless constructors as I need Views to get ViewModels injected. ViewModels to get repositories injected.

When I've created 2nd ocnstructor which is parameterless, there's strange things happening - Views doesn't load inside MainWindow or load, but doesn't use ViewModels.

If I'm setting DataContext in xaml file, Views got parameterless constructors, but then ViewModels must have parameterless constructors...

    <UserControl.DataContext>
        <vm:SettingsViewModel/>
    </UserControl.DataContext>

How to correctly use Dependency injection in my case?


1
You should use viewmodel first and template them into ui rather than instantiating views directly in xaml.Andy
@Andy Could You please explain how to use ViewModel first? I think I found something about it 30 minutes ago and I'm studying it. mesta-automation.com/…Genotypek

1 Answers

2
votes

WPF requires objects which are instantiated in XAML to define a parameterless constructor.

There are many ways to accomplish Dependency Injection in WPF using MVVM. When searching the internet the most wide spread solution seems to be the ViewModelLocator, another Service Locator implementation which is considered widely an anti-pattern (like the infamous static Singleton IoC container).

A simple solution is to use composition. You create a main view model which is composed of other view models where each is dedicated to a certain view.

MainViewModel.cs

class MainViewModel
{
  public MainViewModel(IFirstControlViewModel firstControlViewModel ,
    ISecondControlViewModel secondControlViewModel)
  { 
    this.FirstControlViewModel = firstControlViewModel;
    this.SecondControlViewModel = secondControlViewModel;
  }  

  public IFirstControlViewModel FirstControlViewModel { get; }
  public ISecondControlViewModel SecondViewModel { get; }
}

FirstViewModel.cs

class FirstViewModel : IFirstViewModel
{      
}

SecondViewModel.cs

class SecondViewModel : ISecondViewModel
{
  public SecondVieModel(IThirdViewModel thirdViewModel) => this.ThirdViewModel = thirdViewModel;

  public IThirdViewModel ThirdViewModel { get; } 
}

MainWindow.xaml

<Window>
  <StackPanel>
    <FirstUserControl DataContext="{Binding FirstViewModel}" />
    <SecondUserControl DataCOntext="{Binding SecondViewModel}" />
  </StackPanel>
</Window>

SecondUserControlxaml

<UserControl>
  <Grid>
    <ThirdUserControl DataContext="{Binding ThirdViewModel}" />
  </Grid>
</UserControl>

App.xaml.cs

private void Run(StartupEventArgs e)
{
  IMainViewModel viewModel = container.GetExportedValue<IMainViewModel>();
  var mainWindow = new MainWindow { DataContext = viewModel };
  mainWindow.Show();
}

Or use only top-level composition:

MainViewModel.cs

class MainViewModel
{
  public MainViewModel(IFirstControlViewModel firstControlViewModel ,
    ISecondControlViewModel secondControlViewModel,
    IThirdViewModel thirdViewModel)
  { 
    this.FirstControlViewModel = firstControlViewModel;
    this.SecondControlViewModel = secondControlViewModel;
    this.ThirdViewModel = thirdViewModel;
  }  

  public IFirstControlViewModel FirstControlViewModel { get; }
  public ISecondControlViewModel SecondViewModel { get; }
  public IThirdViewModel ThirdViewModel { get; } 
}

App.xaml.cs

private void Run(StartupEventArgs e)
{
  IMainViewModel viewModel = container.GetExportedValue<IMainViewModel>();

  // For simplicity, you can add the view model to the globally accessible App.xaml ResourceDictionary
  this.Resources.Add("MainViewModel", viewModel);

  var mainWindow = new MainWindow { DataContext = viewModel };
  mainWindow.Show();
}

SecondUserControlxaml

<UserControl>
  <Grid>
    <ThirdUserControl DataContext="{Binding Source="{StaticResource MainViewModel}", Path=ThirdViewModel}" />
  </Grid>
</UserControl>

Composition is a very simple solution to use Dependency Injection with views. If performance e.g. classes with a big dependency tree is an issue many DI frameworks like MEF support Lazy<T> exports.