2

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?


2
  • 1
    You should use viewmodel first and template them into ui rather than instantiating views directly in xaml. Commented Jan 20, 2020 at 20:09
  • @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/… Commented Jan 20, 2020 at 20:11

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm almost there, MainWindow has it's DataContext. Last thing is that using this: <views:SettingsView DataContext="{Binding SettingsViewModel}"/> doesn't set SettingsView DataContext. Whole controll is like a dummy, all labels which should be initially invisible are visible and have no color from Converters. Are You able to deduce why it doesn't work as described? MainViewModel has SettingsViewModel obtained by dependency injection and I'm binding to this instance.
Nevermind. My mistake. Everything works. Just some properties were not public. It works brilliant, thank You so much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.