So I've just recently started using the Microsoft.Extensions.DependencyInjection
nuget package in my WPF project because I wanted to start learning more about DI.
Issue
I keep getting a Circular Dependency Exception
whenever I try to access a dependency from any other ViewModel
besides the MainViewModel
This is what I have done so far.
I've installed these two Nuget packages into my project
Microsoft.Extensions.Hosting --version 7.0.0
Microsoft.Extensions.DependencyInjection --version 7.0.0
And then I went ahead and created my container inside my App.xaml.cs
public partial class App : Application
{
private readonly ServiceProvider _serviceProvider;
public App()
{
IServiceCollection _services = new ServiceCollection();
_services.AddSingleton<MainViewModel>();
_services.AddSingleton<HomeViewModel>();
_services.AddSingleton<SettingsViewModel>();
_services.AddSingleton<DataService>();
_services.AddSingleton<NavService>();
_services.AddSingleton<MainWindow>(o => new MainWindow
{
DataContext = o.GetRequiredService<MainViewModel>()
});
_serviceProvider = _services.BuildServiceProvider();
}
protected override void OnStartup(StartupEventArgs e)
{
var MainWindow = _serviceProvider.GetRequiredService<MainWindow>();
MainWindow.Show();
base.OnStartup(e);
}
}
In my App.xaml
I also defined a few DataTemplates
which will allow me to display different views based in their DataType
<Application.Resources>
<DataTemplate DataType="{x:Type viewModel:HomeViewModel}">
<view:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SettingsViewModel}">
<view:SettingsView/>
</DataTemplate>
</Application.Resources>
Then I went ahead and created my MainWindow.xaml
<Window x:Class="Navs.MainWindow"
...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border>
<StackPanel>
<Button Height="25" Content="Home" Command="{Binding HomeViewCommand}"/>
<Button Height="25" Content="Settings" Command="{Binding SettingsViewCommand}"/>
</StackPanel>
</Border>
<ContentControl Grid.Column="1" Content="{Binding NavService.CurrentView}">
</ContentControl>
</Grid>
</Window>
And a corresponding ViewModel
public class MainViewModel : ObservableObject
{
private NavService _navService;
public NavService NavService
{
get => _navService;
set
{
_navService = value;
OnPropertyChanged();
}
}
/* Commands */
public RelayCommand SettingsViewCommand { get; set; }
public RelayCommand HomeViewCommand { get; set; }
public MainViewModel(NavService navService, HomeViewModel homeViewModel, SettingsViewModel settingsViewModel)
{
NavService = navService;
HomeViewCommand = new RelayCommand(o => true, o => { NavService.CurrentView = homeViewModel; });
SettingsViewCommand = new RelayCommand(o => true, o => { NavService.CurrentView = settingsViewModel; });
}
}
As you can see, with the help of Dependency Injection, I can now access the objects I've registered in my container through the constructor.
I've also created two UserControls
UserControl1
<Grid>
<StackPanel VerticalAlignment="Center">
<Button Height="25" Content="Click me" Command="{Binding OpenWindowCommand}" />
<Button Content="Settings View" Command="{Binding SettingsViewCommand}" Height="25" />
</StackPanel>
</Grid>
And it's corresponding ViewModel
public class HomeViewModel
{
public RelayCommand SettingsViewCommand { get; set; }
public HomeViewModel()
{
}
}
And then we have UserControl2
<Grid>
<StackPanel VerticalAlignment="Center">
<TextBox Text="{Binding Message}"
Height="25"/>
<Button Height="25" Content="Home View" Command="{Binding HomeViewCommand}"/>
<Button Height="25" Content="Fetch" Command="{Binding FetchDataCommand}"/>
</StackPanel>
</Grid>
With it's corresponding ViewModel
public class SettingsViewModel : ObservableObject
{
public string Message { get; set; }
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand FetchDataCommand { get; set; }
public SettingsViewModel()
{
}
}
The NavService.cs
public class NavService : ObservableObject
{
private object _currentView;
public object CurrentView
{
get => _currentView;
set
{
_currentView = value;
OnPropertyChanged();
}
}
private HomeViewModel HomeViewModel { get; set; }
private SettingsViewModel SettingsViewModel { get; set; }
public NavService(HomeViewModel homeViewModel, SettingsViewModel settingsViewModel)
{
HomeViewModel = homeViewModel;
SettingsViewModel = settingsViewModel;
CurrentView = HomeViewModel;
}
public void NavigateTo(string viewName)
{
switch (viewName)
{
case "Settings":
CurrentView = SettingsViewModel;
break;
case "Home":
CurrentView = HomeViewModel;
break;
}
}
}
This all works just fine, the issue occurs when I take the HomeViewModel
and try to pass in the NavService
as a constructor.
public HomeViewModel(NavService navService)
{
}
At that point, it throws the exception.
I want to be able to access the NavService
from various Views
so that I can change the NavService.CurrentView
from multiple places.