Search code examples
c#mvvmdependency-injectionnavigationwinui-3

WinUI - Dependency Injection and Window Navigation


I am trying to understand how to properly use dependency injection for Navigating to different pages on my application. Currently just a Main Window & a Login Window. Now that I have my Windows stored in the NavigationService._windows, how do I create / access an instance of that window?

Thanks for your help.

This is the relevant code in App.xaml.cs:

    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs e)
    {
        Container = RegisterServices();

        _loginWindow = new LoginWindow();
        _loginWindow.Activate();
    }

    private IServiceProvider RegisterServices()
    {
        var services = new ServiceCollection();
        var navigationService = new NavigationService();

        // Register Windows:
        navigationService.Configure(nameof(LoginWindow), typeof(LoginWindow));
        navigationService.Configure(nameof(MainWindow), typeof(MainWindow));

        services.AddSingleton<INavigationService>(navigationService);

        // Register ViewModels:
        services.AddTransient<LoginViewModel>();
        services.AddSingleton<MainViewModel>();

        return services.BuildServiceProvider();
    }

Here is my NavigationService class where the two windows are registered into:

public class NavigationService : INavigationService
{
    private readonly IDictionary<string, Type> _windows = new ConcurrentDictionary<string, Type>();
    // Interface Implementation
    public string CurrentWindow => throw new NotImplementedException();

    public void GoBack()
    {
        throw new NotImplementedException();
    }

    public void NavigateTo(string window, object parameter = null)
    {
        if (!_windows.ContainsKey(window))
        {
            throw new ArgumentException($"Unable to find a page registered with the name {window}.");
        }

        // Code to navigate to window should go here...
        // this is where I'm a bit stuck.
        throw new NotImplementedException();
    }

    // Class Functions:
    public void Configure(string window, Type type)
    {
        if (_windows.Values.Any(v => v == type))
        {
            throw new ArgumentException($"The {type.Name} view has already been registered under another name.");
        }

        _windows[window] = type;
    }
}

Solution

  • I'm not clear from the code you supplied how you're trying to use DI in your application. But here's an approach I've been using:

    public partial class App : Application
    {
        public new static App Current => (App)Application.Current;
    
        public IJ4JHost Host { get; }
    
        public App()
        {
            this.InitializeComponent();
    
            var hostConfig = new J4JHostConfiguration()
                            .Publisher( "J4JSoftware" )
                            .ApplicationName( "GPSLocator" )
                            .LoggerInitializer( InitializeLogger )
                            .AddNetEventSinkToLogger()
                            .AddDependencyInjectionInitializers( SetupDependencyInjection )
                            .AddServicesInitializers( InitializeServices )
                            .AddUserConfigurationFile( "userConfig.json", optional: true, reloadOnChange: true )
                            .AddApplicationConfigurationFile( "appConfig.json", optional: false )
                            .FilePathTrimmer( FilePathTrimmer );
    
            if (hostConfig.MissingRequirements != J4JHostRequirements.AllMet)
                throw new ApplicationException($"Missing J4JHostConfiguration items: {hostConfig.MissingRequirements}");
    
            Host = hostConfig.Build()
             ?? throw new NullReferenceException($"Failed to build {nameof(IJ4JHost)}");
        }
    }
    

    Without going into the details of how my host configuration builder works, basically it has a DI setup component (along with a bunch of other stuff, like logging and command line processing) which you can access via a Services property exposed on IJ4JHost.

    This setup essentially makes the App class the viewmodel locator, if that term means anything to you.

    For example, if I need an instance of a particular viewmodel someplace I would get it like this:

    Configuration = App.Current.Host.Services.GetRequiredService<AppConfig>();
    

    If you want to see more details on how my host builder works you can check out its github repository. It's located in the DependencyInjection project folder.

    You can see examples of how I use this in action at a github repository for a GPS locator app I'm writing.