WPF/MVVM/Caliburn架构下WebView2自定义数据目录调用位置问题
现有项目配置
Bootstrapper中ViewModel注册代码
builder.RegisterType<CustomWebViewModel>().As<Screen>();
CustomWebView.xaml中WebView2初始定义
<Grid> <wv2:WebView2 x:Name="WebView2" Source="{Binding WebAddress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" /> </Grid>
问题现象
开发环境下应用运行正常,WebView2可正确加载展示内容;生产环境应用安装在C:\Program Files\Company\Product\目录时,启动抛出错误:
Microsoft Edge can't read and write to its data directory: C:\Program Files\Company\Product\Product.exe.WebView2\EBWebView
故障根因
WebView2默认将用户数据目录创建在应用可执行文件同级目录下,普通用户无Program Files目录的写入权限,因此需要自定义具备读写权限的用户数据目录。已编写自定义环境初始化代码如下:
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); var userDataFolder = System.IO.Path.Combine(appData, "CustomWebView2"); var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder); var webView2 = (WebView2)this.FindName("WebView2"); if (webView2 != null) await webView2.EnsureCoreWebView2Async(env);
重复初始化异常原因
此前在OnCoreWebView2InitializationCompleted、OnContentLoading事件中调用上述代码抛出如下异常:
System.ArgumentException: 'WebView2 was already initialized with a different CoreWebView2Environment. Check to see if the Source property was already set or EnsureCoreWebView2Async was previously called with different values.'
异常触发的核心原因是XAML中预先给WebView2设置了Source绑定,WebView2控件只要检测到Source属性被赋值,就会自动使用默认环境参数触发初始化,上述两个事件的触发时机都晚于自动初始化的执行节点,此时再传入自定义环境调用初始化方法,必然抛出重复初始化错误。
正确实现方式
- 首先修改XAML代码,移除WebView2的
Source属性预先绑定,阻断控件自动初始化触发条件:
<Grid> <wv2:WebView2 x:Name="WebView2" /> </Grid>
- 将自定义环境初始化逻辑放在View的
Loaded事件中执行(Caliburn.MVVM架构下可直接在View的code-behind中编写,不违反MVVM原则;若要严格规避code-behind可通过附加属性实现),初始化完成后再为WebView2赋值Source:
// CustomWebView.xaml.cs private async void UserControl_Loaded(object sender, RoutedEventArgs e) { // 避免Loaded事件多次触发重复初始化 if (WebView2.CoreWebView2 != null) return; var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); var userDataFolder = Path.Combine(appData, "CustomWebView2"); // 提前创建目录,避免极端情况下目录不存在引发异常 Directory.CreateDirectory(userDataFolder); var webEnv = await CoreWebView2Environment.CreateAsync(null, userDataFolder); await WebView2.EnsureCoreWebView2Async(webEnv); // 初始化完成后再赋值页面地址,替代原有XAML绑定逻辑 if (DataContext is CustomWebViewModel vm) { WebView2.Source = new Uri(vm.WebAddress); } }
如果需要保留ViewModel驱动地址变更的能力,可在初始化完成后,监听ViewModel的WebAddress属性变更事件,同步更新WebView2的Source属性即可。
内容的提问来源于stack exchange,提问作者IgorStack

