MAUI中Shell.Current.GoToAsync跳转NotesApiPage触发空引用异常
MAUI登录后跳转NotesApiPage触发空引用异常的排查与解决
问题描述
在MAUI项目中通过ViewModel实现登录状态校验:无认证信息时跳转LoginPage功能正常,但跳转NotesApiPage时触发System.NullReferenceException: 'Object reference not set to an instance of an object.'异常。
相关代码片段
ViewModel代码
using Notas.Helpers; using Notas.Model; using Notas.View; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace Notas.ViewModel { public partial class LoadingPageViewModel : BaseViewModel { public LoadingPageViewModel() { CheckUserLoginDetails(); } private async void CheckUserLoginDetails() { // retrieve token from internal storage var token = await SecureStorage.GetAsync("Token"); if (string.IsNullOrEmpty(token)) await GoToLoginPage(); else { var jsonToken = new JwtSecurityTokenHandler().ReadJwtToken(token); if (jsonToken.ValidTo < DateTime.UtcNow) { SecureStorage.Remove("Token"); await GoToLoginPage(); } else { var role = jsonToken.Claims.FirstOrDefault(j => j.Type.Equals(ClaimTypes.Role))?.Value; App.UserInfo = new UserInfo() { username = jsonToken.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Name))?.Value, role = role }; MenuBuilder.BuildMenu(); await GoToMainPage(); } } } private async Task GoToLoginPage() { await Shell.Current.GoToAsync($"{nameof(LoginPage)}"); } private async Task GoToMainPage() { await Shell.Current.GoToAsync($"{nameof(NotesApiPage)}"); //GETTING ERROR HERE... } } }
NotesApiPage的XAML代码
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Notas.View.NotesApiPage" xmlns:model="clr-namespace:Notas.Model" xmlns:viewmodel="clr-namespace:Notas.ViewModel" x:DataType="viewmodel:NotesApiViewModel" Title="{Binding Title}"> <VerticalStackLayout HorizontalOptions="Start" Padding="5" Spacing="10" Margin="5"> <Label Text="My Notes" FontAttributes="Bold" FontSize="Subtitle"/> <RefreshView IsRefreshing="{Binding Isrefreshing}" Command="{Binding GetNotesCommand}"> <CollectionView ItemsSource="{Binding Notes}"> <CollectionView.ItemTemplate> <DataTemplate x:DataType="model:Note"> <Grid Padding="3" ColumnSpacing="2" ColumnDefinitions="*,Auto"> <Frame Grid.Column="0"> <Frame.GestureRecognizers> <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:NotesViewModel}}, Path=GetNoteDetailsCommand}" CommandParameter="{Binding Id}"/> </Frame.GestureRecognizers> <Label Text="{Binding NoteDescription}"/> </Frame> <Button Grid.Column="1" Text="Delete" Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:NotesViewModel}}, Path=DeleteNoteCommand}" CommandParameter="{Binding Id}"/> </Grid> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </RefreshView> </VerticalStackLayout> </ContentPage>
AppShell代码
<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="Notas.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Notas.View" Shell.FlyoutBehavior="Flyout"> <ShellContent Title="Home" Icon="icons_home.png" ContentTemplate="{DataTemplate local:LoadingPage}" Route="LoadingPage" /> </Shell>
AppShell.xaml.cs代码
using Notas.View; namespace Notas; public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute(nameof(NotesApiPage), typeof(NotesApiPage)); Routing.RegisterRoute(nameof(NoteDetailPage), typeof(NoteDetailPage)); Routing.RegisterRoute(nameof(LoginPage), typeof(LoginPage)); Routing.RegisterRoute(nameof(MainPage), typeof(MainPage)); } }
原因分析
- Binding上下文匹配错误:NotesApiPage的
x:DataType指定为NotesApiViewModel,但DataTemplate内部的TapGestureRecognizer和Button绑定的AncestorType是NotesViewModel,找不到对应的ViewModel实例,导致绑定的Command为null,触发空引用异常。 - ViewModel初始化缺失:如果NotesApiPage未正确设置BindingContext(比如未在后台代码或XAML中初始化
NotesApiViewModel实例),页面绑定的Title、Isrefreshing等属性会因为找不到ViewModel而触发空引用。 - 异步方法调用时机问题:LoadingPageViewModel构造函数中直接调用
async void方法CheckUserLoginDetails(),可能在Shell尚未完全初始化完成时执行导航操作,极端情况下会导致Shell.Current为null。
解决方法
- 修正DataTemplate绑定源:将NotesApiPage XAML中DataTemplate内的绑定
AncestorType改为viewmodel:NotesApiViewModel,与页面的ViewModel类型保持一致:
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:NotesApiViewModel}}, Path=GetNoteDetailsCommand}" CommandParameter="{Binding Id}"/>
<Button Grid.Column="1" Text="Delete" Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:NotesApiViewModel}}, Path=DeleteNoteCommand}" CommandParameter="{Binding Id}"/>
- 确保NotesApiPage的BindingContext初始化:在NotesApiPage的后台代码构造函数中添加ViewModel实例初始化:
public NotesApiPage() { InitializeComponent(); BindingContext = new NotesApiViewModel(); }
- 调整异步方法调用时机:将LoadingPageViewModel中的
CheckUserLoginDetails()调用从构造函数移至页面的OnAppearing方法,避免Shell未初始化完成时执行导航:
- 修改LoadingPage.xaml.cs:
protected override async void OnAppearing() { base.OnAppearing(); if (BindingContext is LoadingPageViewModel vm) { await vm.CheckUserLoginDetails(); } }
- 同时将LoadingPageViewModel中的
CheckUserLoginDetails()改为返回Task:
private async Task CheckUserLoginDetails() { // 原有逻辑不变 }
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

