0

Using Template Studio for WinUI, I created an app with 2 pages, Main and Foo. If the view model is passed into the page's constructor the application generates an exception, but if the view model is grabbed using App.GetService<xViewModel>(); where x is the name of the view model for the page, everything works great. Passing the same view model into other non-page class constructors works great as well.

Can someone explain why passing the view model into the page's constructor fails?

Code

Generated Foo.xaml.cs file (works great):

using Ioc.ViewModels;
using Microsoft.UI.Xaml.Controls;

namespace Ioc.Views;

public sealed partial class FooPage : Page
{
    public FooViewModel ViewModel
    {
        get;
    }

    public FooPage()
    {
        ViewModel = App.GetService<FooViewModel>();
        InitializeComponent();
    }
}

Foo.xaml.cs - passing view model into the constructor (throws exception):

    public FooPage(FooViewModel viewModel)
    {
        ViewModel = viewModel;
        InitializeComponent();
    }

the following exception occurs when trying to navigate to the Foo page when view model is passed into the constructor:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

at Ioc.Ioc_XamlTypeInfo.XamlUserType.ActivateInstance() in C:\PathToProject\Ioc\obj\x64\Debug\net7.0-windows10.0.19041.0\win10-x64\XamlTypeInfo.g.cs:line 2602

App.xaml.cs constructor where the FooViewModel service is registered:

    public App()
    {
        InitializeComponent();

        Host = Microsoft.Extensions.Hosting.Host.
        CreateDefaultBuilder().
        UseContentRoot(AppContext.BaseDirectory).
        ConfigureServices((context, services) =>
        {
            // Default Activation Handler
            services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();

            // Other Activation Handlers

            // Services
            services.AddTransient<INavigationViewService, NavigationViewService>();

            services.AddSingleton<IActivationService, ActivationService>();
            services.AddSingleton<IPageService, PageService>();
            services.AddSingleton<INavigationService, NavigationService>();

            // Core Services
            services.AddSingleton<IFileService, FileService>();

            // Views and ViewModels
            services.AddTransient<FooViewModel>();
            services.AddTransient<FooPage>();
            services.AddTransient<MainViewModel>();
            services.AddTransient<MainPage>();
            services.AddTransient<ShellPage>();
            services.AddTransient<ShellViewModel>();

            // Configuration
        }).
        Build();

        UnhandledException += App_UnhandledException;
    }

Edit

Navigation using a frame and this Navigation method:

private Frame? _frame; // Type Microsoft.UI.Xaml.Controls.Frame

    public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false)
    {
        var pageType = _pageService.GetPageType(pageKey);

        if (_frame != null && (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed))))
        {
            _frame.Tag = clearNavigation;
            var vmBeforeNavigation = _frame.GetPageViewModel();
            var navigated = _frame.Navigate(pageType, parameter);
            if (navigated)
            {
                _logger.LogDebug("Navigated to {PageType}", pageType.Name);
                _lastParameterUsed = parameter;
                if (vmBeforeNavigation is INavigationAware navigationAware)
                {
                    navigationAware.OnNavigatedFrom();
                }
            }

            return navigated;
        }

        return false;
    }

The code throws the exception on this line:

var navigated = _frame.Navigate(pageType, parameter);
JasonC
  • 139
  • 8

1 Answers1

1

By navigate I guess you mean navigation of a Frame control but the navigation feature of Frames doesn't resolve your type like App.GetService<T>() does.

What you can do is something like this:

In App.xaml.cs

public static bool TryGetService(Type serviceType, out object? service)
{
    service = (App.Current as App)?.Host?.Services.GetService(serviceType);
    return service is not null;
}

and in the navigation event handler:

private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
    if (args.SelectedItem is NavigationViewItem navigationViewItem &&
        navigationViewItem.Tag is string pageName &&
        Type.GetType(pageName) &&
        App.TryGetService(pageType, out object? page) is true)
    {
        this.ContentFrame.Content = page;
    }
}
``
Andrew KeepCoding
  • 7,040
  • 2
  • 14
  • 21
  • I edited the question to include the navigate being used. Can you explain what you mean by: "the navigation feature of frames doesn't resolve my type like App.GetService() does." – JasonC May 22 '23 at 03:28
  • The ``Frame``'s ``Navigate()`` method has nothing to do with what you register in the ``ConfigureServices`` method. It will just try to use the default constructor ``FooPage()`` but it will fail because ``FooPage(FooViewModel viewModel) is declared. – Andrew KeepCoding May 22 '23 at 04:26
  • It seems XAML requires objects to have a parameterless constructor. Since FooPage is a XAML file the Frames Navigate method seems to be trying to call FooPage's parameterless constructor which does not exist and throws the exception. From Microsoft's XAML overview [Each instance is created by calling the parameterless constructor of the underlying type when parsing and loading the XAML.](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/xaml/?view=netdesktop-7.0) – JasonC May 22 '23 at 23:30