WinUI模板Studio中页面构造注入ViewModel触发空引用异常问题
页面构造函数注入ViewModel失败的原因分析
问题背景
使用Template Studio for WinUI创建包含Main和Foo页面的应用时出现异常:
- 若通过构造函数传入ViewModel,应用触发空引用异常
- 但通过
App.GetService<xViewModel>()获取ViewModel、或把同一ViewModel注入非页面类构造函数时,运行完全正常
异常信息
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
相关代码片段
正常运行的Foo.xaml.cs
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
public sealed partial class FooPage : Page { public FooViewModel ViewModel { get; } public FooPage(FooViewModel viewModel) { ViewModel = viewModel; InitializeComponent(); } }
App.xaml.cs服务注册代码
public App() { InitializeComponent(); Host = Microsoft.Extensions.Hosting.Host. CreateDefaultBuilder(). UseContentRoot(AppContext.BaseDirectory). ConfigureServices((context, services) => { // Default Activation Handler services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>(); // 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>(); }). Build(); UnhandledException += App_UnhandledException; }
导航触发代码
private Frame? _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; }
核心原因
问题出在WinUI的XAML类型激活机制与依赖注入(DI)容器的交互逻辑:
- 调用
Frame.Navigate(pageType)时,WinUI默认使用自动生成的XamlUserType.ActivateInstance()方法创建页面实例,该方法仅支持调用无参构造函数初始化对象。 - 若页面仅提供带ViewModel参数的构造函数,XAML激活器找不到合法实例化方式,会返回
null,后续代码引用空对象时触发空引用异常。 App.GetService<xViewModel>()能正常工作,是因为页面保留了无参构造,XAML激活器可正常创建页面实例,之后页面主动从DI容器获取ViewModel,绕开了XAML激活的限制。- 非页面类注入正常,是因为这类对象完全由DI容器负责解析,无需经过WinUI的XAML激活流程,构造函数注入可正常生效。
可行解决方案
方案1:保留无参构造(推荐,符合Template Studio默认规范)
维持正常运行的代码结构,页面保留无参构造,在构造函数内通过DI容器获取ViewModel:
public FooPage() { ViewModel = App.GetService<FooViewModel>(); InitializeComponent(); }
方案2:自定义导航逻辑,通过DI容器获取页面实例
修改导航方法,直接从DI容器解析页面实例,而非依赖Frame.Navigate()的默认激活逻辑:
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)))) { return false; } _frame.Tag = clearNavigation; var vmBeforeNavigation = _frame.GetPageViewModel(); // 从DI容器获取页面实例 var page = App.Host.Services.GetRequiredService(pageType) as Page; if (page == null) return false; // 处理导航参数(若需传递参数给ViewModel) if (parameter != null && page.DataContext is INavigationAware navigationAware) { navigationAware.OnNavigatedTo(parameter); } _frame.Content = page; _logger.LogDebug("Navigated to {PageType}", pageType.Name); _lastParameterUsed = parameter; if (vmBeforeNavigation is INavigationAware navAware) { navAware.OnNavigatedFrom(); } return true; }
内容的提问来源于stack exchange,提问作者JasonC
相关产品推荐
相关产品推荐

