You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)容器的交互逻辑:

  1. 调用Frame.Navigate(pageType)时,WinUI默认使用自动生成的XamlUserType.ActivateInstance()方法创建页面实例,该方法仅支持调用无参构造函数初始化对象。
  2. 若页面仅提供带ViewModel参数的构造函数,XAML激活器找不到合法实例化方式,会返回null,后续代码引用空对象时触发空引用异常。
  3. App.GetService<xViewModel>()能正常工作,是因为页面保留了无参构造,XAML激活器可正常创建页面实例,之后页面主动从DI容器获取ViewModel,绕开了XAML激活的限制。
  4. 非页面类注入正常,是因为这类对象完全由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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 05:07:23