使用MAUI Shell导航传递对象时触发InvalidCastException异常
问题分析与解决方案
问题根源
Shell导航的GoToAsync(IDictionary<string, object>)重载并非设计用于传递复杂自定义对象,它底层会尝试将字典中的所有值转换为可序列化的URL查询参数类型(如字符串、数值等)。你的Training类未实现IConvertible接口,因此触发InvalidCastException。
解决方案
方案1:使用全局对象缓存传递复杂对象(推荐)
实现一个简单的参数管理器,通过唯一ID暂存对象,导航时传递ID,目标页面通过ID取回对象:
// 全局参数管理器 public static class NavigationParameterManager { private static readonly Dictionary<string, object> _parameters = new(); private static readonly Random _random = new(); public static string StoreParameter(object param) { var key = _random.Next(10000, 99999).ToString(); _parameters[key] = param; return key; } public static T? RetrieveParameter<T>(string key) { if (_parameters.TryGetValue(key, out var value) && value is T param) { _parameters.Remove(key); // 用完即删,避免内存泄漏 return param; } return default; } }
修改导航代码:
[RelayCommand] async Task OpenEntry(Training training) { var paramKey = NavigationParameterManager.StoreParameter(training); await Shell.Current.GoToAsync($"{nameof(DetailPage)}?ParamKey={paramKey}"); }
修改目标ViewModel:
[QueryProperty(nameof(ParamKey), "ParamKey")] public partial class DetailViewModel : ObservableObject { [ObservableProperty] string? paramKey; [ObservableProperty] Training? detailedTraining; partial void OnParamKeyChanged(string? oldValue, string? newValue) { if (!string.IsNullOrEmpty(newValue)) { DetailedTraining = NavigationParameterManager.RetrieveParameter<Training>(newValue); } } }
方案2:序列化为JSON字符串传递
将Training对象序列化为JSON字符串,作为普通字符串参数传递,目标页面反序列化:
导航代码:
[RelayCommand] async Task OpenEntry(Training training) { var json = JsonSerializer.Serialize(training); // 注意对URL进行编码 var encodedJson = Uri.EscapeDataString(json); await Shell.Current.GoToAsync($"{nameof(DetailPage)}?TrainingJson={encodedJson}"); }
目标ViewModel:
[QueryProperty(nameof(TrainingJson), "TrainingJson")] public partial class DetailViewModel : ObservableObject { [ObservableProperty] string? trainingJson; [ObservableProperty] Training? detailedTraining; partial void OnTrainingJsonChanged(string? oldValue, string? newValue) { if (!string.IsNullOrEmpty(newValue)) { var decodedJson = Uri.UnescapeDataString(newValue); DetailedTraining = JsonSerializer.Deserialize<Training>(decodedJson); } } }
方案3:直接获取目标ViewModel赋值(适用于简单场景)
如果目标页面已注册到Shell,可以直接获取其ViewModel并赋值,再导航:
[RelayCommand] async Task OpenEntry(Training training) { // 获取目标页面实例 var detailPage = (DetailPage)Shell.Current.CurrentPage.Navigation.NavigationStack.FirstOrDefault(p => p is DetailPage) ?? new DetailPage(); // 直接赋值ViewModel属性 ((DetailViewModel)detailPage.BindingContext).DetailedTraining = training; await Shell.Current.GoToAsync(nameof(DetailPage)); }
注意事项
- 避免直接用
IDictionary<string, object>传递复杂对象,Shell的该重载仅支持基础数据类型。 - 使用全局缓存时,记得在取回对象后移除缓存,防止内存泄漏。
- JSON序列化方案需要确保
Training类的属性支持序列化(无循环引用、可访问的公共属性等)。
内容的提问来源于stack exchange,提问作者Nico Sz
相关产品推荐
相关产品推荐

