WPF WebView2导航离开视图时报Source属性无法设为null错误
要完成API服务身份验证,需引导用户登录指定网页。若登录校验通过,身份验证码会追加到重定向Uri的末尾,后续可使用该验证码通过OAuth流程获取访问密钥等凭证。为实现该交互流程,项目中集成了WebView2控件,XAML定义如下:
<wv2:WebView2 Name="webView" Width="800" Height="500" Source="{Binding WebUri, Mode=TwoWay}"> <i:Interaction.Triggers> <i:EventTrigger EventName="SourceChanged"> <i:InvokeCommandAction Command="{Binding SourceChangedCommand}"/> </i:EventTrigger> </i:Interaction.Triggers> </wv2:WebView2>
ViewModel层编写了验证逻辑,用于识别重定向Uri是否包含身份验证码;该逻辑通过WebView2触发SourceChanged事件时调用的SourceChangedCommand执行(WebView2导航事件生命周期可参考微软官方文档),SourceChangedCommand的Execute方法实现如下:
public void Execute(object? parameter) { if (ValidateUri(_viewModel.WebUri.ToString())) { var str = _viewModel.WebUri.ToString().Substring(50); _navigationService.Authenticate(str); } }
项目中实现了NavigationService,负责在不同ViewModel间导航,通过DataTemplate机制切换对应视图。当从承载WebView2的视图导航离开(即修改父视图的数据上下文)时,会抛出如下错误:
The Source property cannot be set to null(Source属性无法设置为null)

排查时存在明显疑点:代码逻辑中从未将WebView2的Source属性设置为null,该属性始终保留为跳转后的重定向Uri值。
目前尝试的备选方案是创建继承自WebView2的自定义控件,新增布尔类型依赖属性映射到WebView2.Dispose(bool)方法,当属性值为true时主动释放WebView2资源,但该方案实现过于冗余,还可能引发ObjectDisposedException相关的其他异常。
这个异常和业务代码无关,是WPF双向绑定机制和WebView2原生属性校验冲突导致的隐式报错:
WebView2的Source依赖属性在控件被卸载销毁的生命周期中,内部会先将自身值重置为null;而代码里给Source配置了TwoWay双向绑定,WPF在检测到依赖属性值变化时,会默认尝试把新值(也就是这个null)回写到绑定源WebUri,这个回写过程会反向触发WebView2对Source属性的赋值校验——WebView2从设计上就不允许Source被赋值为null,直接抛出异常。
由于整个赋值过程是框架隐式执行的,没有对应业务代码显式给Source赋null,排查时很难直接定位触发点。
按改造成本从低到高排序,不需要自定义重写WebView2控件:
- 最优方案:移除不必要的双向绑定
现有业务场景完全不需要给Source配置双向绑定:只需要从ViewModel给控件传递初始导航地址,后续地址变更通过监听SourceChanged事件就能拿到,根本不需要把控件的Source值回写到ViewModel。直接把绑定模式改成OneWay即可彻底解决问题,改动只有一行:
建议同时给<wv2:WebView2 Name="webView" Width="800" Height="500" Source="{Binding WebUri, Mode=OneWay}"> <!-- 其余触发器逻辑保持不变 --> </wv2:WebView2>InvokeCommandAction配置传递事件参数,不要依赖绑定的WebUri取值、也不要用硬编码长度截取字符串,可靠性更高:<i:InvokeCommandAction Command="{Binding SourceChangedCommand}" PassEventArgsToCommand="True"/> - 兼容方案:拦截null值传递
如果因为现有业务逻辑耦合必须保留双向绑定,给绑定配置默认值兜底,同时在ViewModel层拦截null:
首先修改XAML绑定,给null值和绑定失败场景配置合法的默认Uri(WebView2原生支持about:blank作为空白页地址):
然后在ViewModel的Source="{Binding WebUri, Mode=TwoWay, TargetNullValue=about:blank, FallbackValue=about:blank}"WebUri属性setter中增加判断,当传入value为null时,直接赋值为new Uri("about:blank"),从源头阻止null值传递到控件。 - 兜底方案:视图卸载时主动清除绑定
如果以上两个方案都不适用,可以在承载WebView2的视图的Unloaded生命周期事件中,手动清除Source属性的绑定,断开绑定链路后控件销毁时的属性变更就不会触发回写逻辑:private void View_Unloaded(object sender, RoutedEventArgs e) { webView.ClearValue(WebView2.SourceProperty); }
之前尝试的自定义控件释放资源的方案完全没必要,不仅冗余,还容易引入控件生命周期相关的其他异常,使用以上三个方案任意一个即可实现正常导航。
内容的提问来源于stack exchange,提问作者jgrmn

