在vsto中使用webview2

用户数据文件夹(UDF)

Webview2需要使用user data folder(UDF),通常默认的UDF是在.exe同目录。

但vsto比较特殊,UDF的默认目录是C:\Program Files (x86)\Microsoft Office\Root\Office16\EXCEL.EXE.WebView2。vsto没有权限访问这个目录。

所以在vsto中使用webview2,必须要设定UDF,比如:

1
2
3
4
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string userDataFolder = Path.Combine(homeDir, "ExcelAddin/UDF");
var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
await webView.EnsureCoreWebView2Async(env);

Administrator账户

但要注意这里有个特例:Administrator账户。

如果用户是Administrator,Webview2界面会一直白屏。调试发现是await webView.EnsureCoreWebView2Async(env);抛出异常:

1
2
3
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.'

原因在WebView2Feedback的这个issue中被提及:
在Designer.cs中的InitializeComponent函数里,this.webView.Source = new System.Uri("about:blank", System.UriKind.Absolute);会使用默认UDF初始化CoreWebView2Environment。

而由于上面提到的原因,非Administrator账户运行的vsto没有权限访问默认目录,会初始化错误。此时反倒没有任何问题。
但如果用户是Administrator,此时初始化成功。而在后续的InitWhenLoaded时又再次使用userDataFolder来EnsureCoreWebView2Async,导致两次使用的CoreWebView2Environment不一致而报错。

解决方案:

  • 【需官方解决】可在webview2控件的属性编辑器中设置Source属性为空
    当在编辑器中设置Source属性为空时,不设置webView.Source,也就不会使用默认UDF初始化CoreWebView2Environment
  • 在InitializeComponent中不设置webview.Source
    注释掉this.webView.Source = new System.Uri("about:blank", System.UriKind.Absolute);
    但因为Designer.cs是编辑器生成的,所以每次修改界面后都要记得手动注释,这个方案不行。
  • try/catch EnsureCoreWebView2Async,针对此情况特殊处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    try
    {
    var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
    await webView.EnsureCoreWebView2Async(env);
    initWebview();
    }
    catch (Exception ex)
    {
    if (ex.Message.StartsWith("WebView2 was already initialized with a different CoreWebView2Environment."))
    {
    await webView.EnsureCoreWebView2Async();
    initWebview();
    }
    else
    {
    MessageBox.Show("EnsureCoreWebView2 error: " + ex.ToString());
    }
    }
    这样更通用些,虽然判断Exception.Message也不是很优雅,但在官方给出解决方案之前可以先用着。

多用户

Excel插件的安装是针对当前用户而不是本机所有用户的。即使Administrator账户安装过,切换至其他用户登录后,如果想使用仍然需要单独安装。

Webview2 Runtime是安装在本机的,只要随便哪个用户安装过一次,其它用户都能够使用。

webview2使用本地html

为了避免多部署一个web服务器,并且要支持不同版本,将html放在本地最合适不过了。

在经历了一系列讨论后,webview2对访问本地文件的方式最终定稿在了SetVirtualHostNameToFolderMapping

这里有C#的示例代码:

1
2
3
4
5
6
7
8
9
10
await myWebview.EnsureCoreWebView2Async(); // ensure the CoreWebView2 is created
var core_wv2 = myWebview.CoreWebView2;
if (core_wv2 != null)
{
core_wv2.SetVirtualHostNameToFolderMapping(
"appassets.example", "assets",
Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);

myWebview.Source = new Uri("https://appassets.example/myVideo.mp4");
}

添加html文件到ClickOnce

普通应用

这里先拿普通桌面应用举例,vsto比较特别,放在后面再看。

首先将index.html添加进工程,这里需要修改index.html的两个属性:

可选值是不复制,始终复制和如果较新则复制。如果选择不复制,只会影响本地调试,并不会影响ClickOnce发布。

当选择为“内容”时,vs会自动将文件放至应用程序文件。可在工程的发布页点击应用程序文件按钮打开应用程序对话框(Application Files Dialog Box)查看。

注意,不要把index.html的发布状态设为数据文件,设为数据文件后index.html就不在安装路径(Installation Path),导致webview2访问不到。

下图是index.html分别为数据文件包括在客户端安装后的文件树对比,ClickOnce应用的安装路径在%LocalAppData%/Apps/2.0/下。

vsto应用

vsto应用虽然在发布页没有这个应用程序文件按钮,

但可以手动修改app manifest,再使用Mage签名。

使用方法可参考How to: Include a Data File in a ClickOnce Application,顺便一提,似乎微软的最新文档在github上有一份,这样更方便查找。

长求总就是:

  1. 修改app manifest(给file标签添加writeableType=”applicationData”属性即为更改文件的发布状态为数据文件
  2. 重新签名app manifest
  3. 更新deployment manifest并重新签名
  4. 将deployment manifest拷贝至app manifest目录下

在vsto中,app manifest为Application Files\ExcelAddin_1_0_0_0\ExcelAddin.dll.manifest,deployment manifest为ExcelAddin.vsto

1
2
3
4
5
6
7
8
9
10
11
12
# 重新签名manifest
dotnet mage -s "Application Files\ExcelAddin_1_0_0_0\ExcelAddin.dll.manifest" -certfile ExcelAddin_ProdKey.pfx -pwd yourpassword

# 重新签名vsto
dotnet mage `
-update ExcelAddin.vsto `
-appmanifest "Application Files\ExcelAddin_1_0_0_0\ExcelAddin.dll.manifest" `
-certfile "ExcelAddin_ProdKey.pfx" `
-pwd yourpassword

# 拷贝vsto
copy "ExcelAddin.vsto" "Application Files\ExcelAddin_1_0_0_0\ExcelAddin.vsto"

依旧无法访问

在普通应用中,SetVirtualHostNameToFolderMapping的起始目录就是工程的输出目录。

但在vsto应用中,这个起始目录不知道在哪。虽然Directory.GetCurrentDirectory()得到的是%UserProfile%\Documents,但把Assets放到Documents下后,webview2仍然无法访问。只能使用绝对路径的file:///

Office解决方案中的数据这篇文章中,微软有介绍vsto应用中可用的数据类型,貌似只有xml和数据库文件。

想把html添加至vsto应用供webview2本地使用的计划似乎破产,只能先放下了,等待机缘。

附录:Mage

Mage的全写是Manifest Generation and Editing Tool,在我们手动修改Manifest.xml后,需要用它来重新签名。

官方说Mage内置在VS中,只要打开Visual Studio Developer Command Prompt or Visual Studio Developer PowerShell就能直接使用。但我打开后却仍然提示找不到Mage命令,找找问题在哪里。

Visual Studio Developer Prompt

顺手一提如何在Windows Terminal中使用Visual Studio Developer Prompt。

Command版本

1
cmd.exe /k "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64

Powershell版本

1
powershell.exe -NoExit -Command "&{Import-Module """C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"""; Enter-VsDevShell 3f987db8 -SkipAutomaticLocation -DevCmdArguments """-arch=x64 -host_arch=x64"""}"

Windows 10 SDK

在Visual Studio Installer中,我们可以选择安装Windows 10 SDK ($Version)。

安装完后,它的安装路径是在C:\Program Files (x86)\Windows Kits\10\bin\$Version,这里面有certmgr.exe等工具。

.NET Framework SDK

在Visual Studio Installer中,我们也可以选择安装.NET $Version Framework SDK。

安装完后,它的安装路径在C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\下,Mage.exe就在这里。

然而$PATH里的却是C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64,这里面并没有Mage.exe。

最简单的解决方法是把Mage.exe和MageUI.exe拷贝过去。

真正的解决方法

原来在.net 5及以后的版本里,Mage.exe被废弃,改用dotnet-mage

1
2
dotnet tool install --global microsoft.dotnet.mage
dotnet mage -help verbose

附录:在安装vsto时拷贝文件

这里有一篇如何部署vsto应用的文档,里面有介绍如何在安装和更新vsto时使用Post-deployment action做文件拷贝的操作。

我们可以在安装和更新vsto时把静态网页文件拷贝到用户目录里,再使用webview2绝对路径来访问。

注意

  1. 在webview2的使用场景里,需要把微软官方示例中的这两行代码删掉,因为我们不是document-level customizations

    1
    2
    ServerDocument.RemoveCustomization(destFile);
    ServerDocument.AddCustomization(destFile, deploymentManifestUri);
  2. 如果postAction抛出异常,vsto会将安装出错的插件卸载,但并不会清除%LocalAppData%/Apps/2.0/下的文件。此时有可能需要手动清除才能恢复正常。

附录:客户机安装webview2运行时

Webview2运行时的安装方式有3种,分别是在线安装,离线安装和指定版本安装。

通常使用离线安装包(安装文件存放在自己服务器上防止外网被墙),可使用命令行参数实现静默安装

1
MicrosoftEdgeWebView2RuntimeInstallerX64.exe /silent /install