在微软的网站上,有关于这一块的说明,阅读前可以先看看这块参考内容:
在 WebView2 应用中处理本地内容
主要的问题在于这几个方面
- 如果使用 使用虚拟主机名映射加载本地内容 的方式来加载页面,自然代码是非常的简单,而且页面从表面看,也启用了https。然而可惜的是,这种方式只能支持http/https的访问方式,而在当前的webview2下,这两种方式均会导致每次加载时有2秒的额外延迟,即使加载就是就是一个静态的html文件。微软的网站上其实也说明了一句:由于当前限制,使用虚拟主机名访问的媒体文件加载速度可能会很慢。
- 如果加载的网页时使用当前流行的vue/react生成的,会发现每次只能加载首页。如果启用了浏览器路由的,这个时候想直接定位到子页面,是不可行的。原因是并不存在直接对应的页面文件,导致webview2读取失败。
- 假设文件很多,这个时候我们想把资源文件打包成一个文件(通常为压缩包),又该如何操作呢?
解决方案的步骤分为如下几步:
- 添加WebResourceRequestedFilter,控制请求数据的读取过程,对特定url进行处理。同时也可以解决压缩包读取问题。
- 读取到未知数据时,重定向到index.html,解决vue/react使用了浏览器路由而无法访问子页面的问题
以下代码从练手项目中摘抄,简单修改后即可运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<Window x:Class="OutlookMailParserAddin.Controls.WebPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:OutlookMailParserAddin.Controls" xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <DockPanel> <DockPanel DockPanel.Dock="Top" Visibility="{Binding WebView2BarVisibility}"> <Button x:Name="ButtonGo" DockPanel.Dock="Right" Click="ButtonGo_Click" Content="Go"/> <Button x:Name="ButtonCall" DockPanel.Dock="Right" Click="ButtonCall_Click" Content="Call"/> <TextBox Name = "addressBar"/> </DockPanel> <DockPanel> <wv2:WebView2 Name="WebView" /> </DockPanel> </DockPanel> </Grid> </Window> |
2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
using Microsoft.Web.WebView2.Core; using System; using System.Collections.Generic; using System.IO; using System.Windows; using Microsoft.Web.WebView2.Wpf; using AddinShared; using OutlookMailParserAddin.misc; using log4net; using System.Web; using Path = System.IO.Path; using System.IO.Compression; using System.Text.Json; namespace OutlookMailParserAddin.Controls { /// <summary> /// WebPage.xaml 的交互逻辑 /// </summary> public partial class WebPage : Window { private static readonly ILog Log = AddinGlobal.GetLogger("WebPage"); private readonly string url; private readonly string filterUrl = $"{AddinGlobal.WebPageUrlRoot}/*"; private readonly string initData; private readonly string toWebView2Message; private readonly WebView2HostObject hostObject; private readonly bool loadFromZipFile = true; public string WebView2BarVisibility { get; set; } = "Collapsed"; public WebPage(string theTitle, string theUrl, string theInitData = null, ICallFromJavaScript theCallFromJavaScript = null) { Title = theTitle; initData = theInitData; url = theUrl; hostObject = new WebView2HostObject(theCallFromJavaScript); InitializeComponent(); this.Loaded += WebPage_Loaded; this.DataContext = this; Dictionary<string, string> args = new Dictionary<string, string>() { { AddinGlobal.WebView2MessageFixedType, AddinGlobal.WebView2MessageFixedTypeValue }, { "data", initData } }; toWebView2Message = JsonSerializer.Serialize(args); // 以下为方便调试使用 string envLoadFromFolder = Environment.GetEnvironmentVariable(AddinShared.EnvironmentVariables.LOAD_WEBPAGE_FROM_FOLDER); if (envLoadFromFolder != null) loadFromZipFile = false; string envShowWebView2Bar = Environment.GetEnvironmentVariable(AddinShared.EnvironmentVariables.MM_SHOW_WEBPAGE_BAR); if( envShowWebView2Bar != null) WebView2BarVisibility = "Visible"; } private void WebPage_Loaded(object sender, RoutedEventArgs e) { WebView.CreationProperties = new CoreWebView2CreationProperties() { UserDataFolder = AddinGlobal.WebviewFolder }; WebView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; if (!string.IsNullOrEmpty(initData)) { WebView.NavigationCompleted += WebViewOnNavigationCompleted; } WebView.EnsureCoreWebView2Async(); } void WebViewOnNavigationCompleted(object o, CoreWebView2NavigationCompletedEventArgs coreWebView2NavigationCompletedEventArgs) { // 移除该事件响应 WebView.NavigationCompleted -= WebViewOnNavigationCompleted; // 更新初始化数据 WebView.CoreWebView2.PostWebMessageAsJson(toWebView2Message); } private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) { #if false // 方式一: 在这里设置虚拟域名,仅支持文件夹读取。这里设置后,就不需要CoreWebView2_WebResourceRequested事件处理了 WebView.CoreWebView2.SetVirtualHostNameToFolderMapping("mailminer", AddinGlobal.WebPageFolder, CoreWebView2HostResourceAccessKind.DenyCors); #endif #if true //方式二: 我们自己处理,可以支持从文件夹或者压缩包中读取数据 WebView.CoreWebView2.AddWebResourceRequestedFilter( filterUrl, CoreWebView2WebResourceContext.All); WebView.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested; #endif WebView.CoreWebView2.AddHostObjectToScript(WebView2HostObject.HostObjectName, hostObject); WebView.CoreWebView2.Navigate(url); } private static readonly Dictionary<string, string> MimeTypes = new Dictionary<string, string> { { ".html", "text/html" }, { ".htm", "text/html" }, { ".css", "text/css" }, { ".js", "application/javascript" }, { ".json", "application/json" }, { ".png", "image/png" }, { ".jpg", "image/jpeg" }, { ".jpeg", "image/jpeg" }, { ".gif", "image/gif" }, { ".svg", "image/svg+xml"} }; private ZipArchive zipArchive; Stream ReadFileFromZip(string zipFilePath, string targetFilePath) { if(zipArchive == null) { zipArchive = ZipFile.OpenRead(zipFilePath); } ZipArchiveEntry entry = zipArchive.GetEntry(targetFilePath); if (entry != null) { // Open the ZIP stream Stream zipStream = entry.Open(); // Create a MemoryStream to read the content into memory MemoryStream memoryStream = new MemoryStream(); zipStream.CopyTo(memoryStream); // Return the MemoryStream return memoryStream; } else { //Console.WriteLine("File not found in ZIP archive."); return null; } } Stream ReadFromFolder(string folder, string subPath) { FileStream fileStream; try { fileStream = File.OpenRead(AddinGlobal.WebPageFolder + "/" + subPath); } catch (Exception ex) { fileStream = null; } return fileStream; } private void CoreWebView2_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args) { string subPath = args.Request.Uri.Substring(filterUrl.Length - 1); Stream stream; try { if ( loadFromZipFile) { // 方式一: 从zip中获取网页数据 stream = ReadFileFromZip(AddinGlobal.WebPageZipFile, subPath); if (stream == null) { subPath = "index.html"; stream = ReadFileFromZip(AddinGlobal.WebPageZipFile, subPath); } } else { //方式二: 从文件夹中获取网页数据, 仅用于调试 stream = ReadFromFolder(AddinGlobal.WebPageFolder, subPath); if (stream == null) { subPath = "index.html"; stream = ReadFromFolder(AddinGlobal.WebPageFolder, subPath); } } string filename = Path.GetFileName(subPath); string fileExtension = Path.GetExtension(filename); ManagedStream ms = new ManagedStream(stream); string headers = ""; bool knownType = MimeTypes.TryGetValue(fileExtension, out var contentType); if( !knownType ) { contentType = MimeMapping.GetMimeMapping(filename); } if (string.IsNullOrEmpty(contentType)) { Log.Warn($"Unknown resource type, path: {subPath}"); // 可能需要修补类型 headers = "Content-Type: application/octet-stream"; // 默认的二进制流类型 } else { headers = $"Content-Type: {contentType}"; } args.Response = WebView.CoreWebView2.Environment.CreateWebResourceResponse( ms, 200, "OK", headers); } catch (Exception e) { Log.Error($"Error, {e.Message}"); args.Response = WebView.CoreWebView2.Environment.CreateWebResourceResponse( null, 404, "Not found", ""); } } public void ButtonGo_Click(object sender, RoutedEventArgs e) { if(WebView.CoreWebView2!= null) { WebView.CoreWebView2.Navigate(addressBar.Text); } } public void ButtonCall_Click(object sender, RoutedEventArgs e) { if(WebView.CoreWebView2!= null) { WebView.CoreWebView2.PostWebMessageAsJson(toWebView2Message); } } } } |
3.
1 2 3 4 5 6 7 |
namespace OutlookMailParserAddin.misc { public interface ICallFromJavaScript { string Call(string identify, string msg); } } |