在 WebView2 應用程式中使用本機內容

除了載入遠端內容之外,內容也可以在本機載入至 WebView2。 有數種方法可用來將本機內容載入 WebView2 控制項,包括:

  • 瀏覽至檔案 URL。
  • 瀏覽至 HTML 字串。
  • 虛擬主機名稱對應。
  • 處理事件 WebResourceRequested 。

詳細內容:

選取方法

將本機內容載入 WebView2 控制項的各種方式支援下列案例:

案例 透過瀏覽至檔案 URL 瀏覽至 HTML 字串 使用虛擬主機名稱對應 使用 WebResourceRequested
以原始來源為基礎的 DOM API 是 ✔️ 否❌ 是 ✔️ 是 ✔️
需要安全內容的 DOM API 否❌ 否❌ 是 ✔️ 是 ✔️
動態內容 否❌ 是 ✔️ 否❌ 是 ✔️
其他 Web 資源 是 ✔️ 否❌ 是 ✔️ 是 ✔️
在 WebView2 程序中解決的其他 Web 資源 是 ✔️ 否❌ 是 ✔️ 否❌

以下說明這些案例。

瀏覽至檔案 URL 以載入本機內容

WebView2 允許瀏覽至檔案 URL、載入基本的 HTML 或 PDF。 這是載入本機內容最簡單、最有效率的方法。 但是,它不如其他方法靈活。 就像在網頁瀏覽器中一樣,檔案 URL 在某些功能上受到限制:

  • 文件的來源對於其檔案路徑而言是唯一的。 這表示需要來源 localStorage 的 Web API 如 或 indexedDB 將可運作,但儲存的資料將無法供從其他檔案路徑載入的其他本機文件使用。

  • 某些 Web API 僅限於安全的 HTTPS URL,無法供檔案 URL 載入的文件使用。 這包括 API,例如 navigator.mediaDevices.getUserMedia() 獲取視頻或聲音、 navigator.geolocation.getCurrentPosition() 訪問設備的位置或 Notification.requestPermission() 請求用戶顯示通知的權限。

  • 必須針對每個資源指定完整路徑。

  • 若要允許從檔案 URI 參照其他本機檔案,或顯示套用 XSL 轉換的 XML 檔案,您可以設定 --allow-file-access-from-files 瀏覽器引數。 請參閱 CoreWebView2EnvironmentOptions.AdditionalBrowserArguments 屬性。

透過瀏覽至檔案 URL 載入本機內容的考量

檔案 URL 的行為就像在瀏覽器中一樣。 例如,您無法在檔案 URL 中進行 XMLHttpRequest (XHR) ,因為您不是在網頁內容中作業。

您必須為每個資源指定檔案的完整路徑。 例如:

file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html
跨原始來源資源

指定檔案 URL 時,應用程式會瀏覽至磁碟上的檔案,而不是網路上的網域。 因此,無法在產生的文件中使用跨原始來源資源。

以原始來源為基礎的 DOM API

透過檔案 URL 載入的文件具有其檔案路徑唯一的來源,就像在瀏覽器中一樣。 需要來源的 Web API,例如 localStorage 或 indexedDB 將可運作。 不過,從不同檔案 URL 載入的不同文件不會被視為來自同一來源,而且無法存取相同的儲存資料。

需要安全內容的 DOM API

某些 Web API 僅限於安全的 HTTPS URL,無法供檔案 URL 載入的文件使用。 這包括 API,例如 navigator.mediaDevices.getUserMedia() 獲取視頻或聲音、 navigator.geolocation.getCurrentPosition() 訪問設備的位置或 Notification.requestPermission() 請求用戶顯示通知的權限。 如需詳細資訊,請參閱 MDN 上的 安全內容 。

動態內容

透過檔案 URL 載入文件時,文件的內容來自磁碟上的靜態檔案。 這表示無法動態修改此本機內容。 這與從 Web 伺服器載入文件不同,在 Web 伺服器中,每個回應都可以動態產生。

其他 Web 資源

相對 URL 解析也適用於透過檔案 URL 載入的文件。 這表示載入的文件可以參考其他 Web 資源,例如 CSS、指令碼或影像檔案,這些資源也會透過檔案 URL 提供。

在 WebView2 程序中解決的其他 Web 資源

檔案 URL 會在 WebView2 處理序中解析。 這是比 WebResourceRequested更快的選項,可在主機應用程式處理序 UI 執行緒中解析。

透過瀏覽至檔案 URL 載入本機內容的 API

檔案 URL 範例

本節以與平台無關的方式顯示本機內容檔案路徑的檔案 URL 外觀。

WebView2 應用程式必須使用首碼和斜線撰寫 file:/// 本機檔案 URL。 例如,對於示範待辦事項範例,路徑為:

file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html

若要複製本機檔案前置詞為 “file” 的完整路徑:

  1. 或者,複製 Demos 儲存庫,以獲得本機複本。 請參閱安裝 Visual Studio Code 的開發工具延伸模組中的步驟 5:複製示範存放庫。

  2. 在 Microsoft Edge 中,按 Ctrl+O 開啟檔案。 開啟本機 .html 檔案,例如本機複製的檔案 Demos/demo-to-do/index.html:

    C:\Users\username\Documents\GitHub\Demos\demo-to-do\index.html

    網址列一開始不會顯示 file:/// 首碼,而是以磁碟機代號開頭:

    C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html
    

    最初隱藏 file:/// 首碼的 Microsoft Edge 的網址列

  3. 按一下網址列,然後按 Home 鍵,或按 Ctrl+A 以選取整個路徑。

    Microsoft Edge 的網址列現在顯示 file:/// 首碼

    包含的整個檔案路徑 file:/// 會複製到剪貼簿緩衝區中,因此您可以貼上包括前置詞在內 file:/// 的完整路徑:

    file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html
    

另請參閱:

瀏覽至檔案 URL 的範例

webView.CoreWebView2.Navigate(
          "file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html");

瀏覽至 HTML 字串載入本機內容

載入本機內容的另一種方法是方法 NavigateToString 。 此方法會直接從字串將內容載入至 WebView2。 如果您要透過應用程式程式碼封裝內容,或想要以動態方式建立內容,這項功能會很有用。

瀏覽至字串可能有用的另一個案例是,如果您想要載入無法透過 URL 存取的內容。 例如,如果您有 HTML 文件的記憶體內部表示法,您可以使用方法將 NavigateToString 該內容載入到 WebView2 控制項中。 如果您想要避免在將內容載入控制項之前,先將內容寫入檔案或伺服器,這個功能就相當實用。

瀏覽至 HTML 字串載入本機內容的考量

傳遞至 NavigateToString 方法的 HTML 內容字串有 2MB 的大小限制。 當字串包含內嵌的其他資源時,可能很容易超過此大小限制。 如果超過此大小限制,則會傳回錯誤:「值不在預期範圍內」。

以原始來源為基礎的 DOM API

使用 NavigateToString 方法載入的文件會將其位置設定為 about:blank ,而其來源設定為 null。 這表示無法使用相依於所定義來源的 Web API,例如 localStorage 或 indexedDB。

需要安全內容的 DOM API

某些 Web API 僅限於安全的 HTTPS URL,而且無法供透過方法 NavigateToString 載入的文件使用,因為它們的位置設定為 about:blank。 這包括 API,例如 navigator.mediaDevices.getUserMedia() 獲取視頻或聲音、 navigator.geolocation.getCurrentPosition() 訪問設備的位置或 Notification.requestPermission() 請求用戶顯示通知的權限。 如需詳細資訊,請參閱 MDN 上的 安全內容 。

動態內容

透過 NavigateToString 方法載入本機內容時,您會直接將內容作為參數提供給方法。 這意味著您可以在運行時控制內容,並且可以在需要時動態生成內容。

其他 Web 資源

使用方法 NavigateToString 載入本機內容並無法讓生成的文件參考其他 Web 資源,例如 CSS、影像或指令碼檔案。 此方法只允許您指定 HTML 文件的字串內容。 若要從 HTML 文件參考其他 Web 資源,請使用本文所述的其他方法之一,或在 HTML 文件中內嵌表示這些額外的 Web 資源。

在 WebView2 程序中解決的其他 Web 資源

NavigateToString 如前所述,不支援其他 Web 資源。

透過瀏覽至 HTML 字串載入本機內容的 API

網頁的字串表示法範例

以下是 [示範待辦事項 ] 網頁的字串表示法。 為了便於閱讀,下列清單已新增自動換行。 實際上,這些行會串連成一條長行:

`<html lang="en"><head>\n   
<meta charset="UTF-8">\n   
<meta name="viewport" content="width=device-width, initial-scale=1.0">\n   
<title>TODO app</title>\n   
<link rel="stylesheet" href="styles/light-theme.css" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)">\n   
<link rel="stylesheet" href="styles/dark-theme.css" media="(prefers-color-scheme: dark)">\n   
<link rel="stylesheet" href="styles/base.css">\n   
<link rel="stylesheet" href="styles/to-do-styles.css">\n   
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📋
</text></svg>">\n 
</head>\n\n 
<body>\n   
<h1>📋 My tasks</h1>\n   
<form>\n     
<div class="new-task-form" tabindex="0">\n       
<label for="new-task">➕ Add a task</label>\n       
<input id="new-task" autocomplete="off" type="text" placeholder="Try typing 'Buy milk'" title="Click to start adding a task">\n       
<input type="submit" value="➡️">\n     
</div>\n     
<ul id="tasks"><li class="divider">No tasks defined</li></ul>\n   
</form>\n\n    \x3Cscript src="to-do.js">\x3C/script>\n  \n\n
</body>
</html>`

若要取得上述字串:

  1. 移至 [示範] To Do。

  2. 以滑鼠右鍵按一下網頁,然後選取 [檢查] 以開啟 DevTools。

  3. 在開發工具主 控台 中,輸入: document.body.parentElement.outerHTML。 主 控台 會輸出網頁的字串表示法:

    示範待辦事項網頁的字串表示法

瀏覽至 HTML 字串的範例

// Define htmlString with the string representation of HTML as above.
webView.CoreWebView2.NavigateToString(htmlString);

使用虛擬主機名稱對應載入本機內容

在 WebView2 控制項中載入本機內容的另一種方法是使用虛擬主機名稱對應。 這涉及將本機網域名稱對應到本機資料夾,因此當 WebView2 控制項嘗試載入該網域的資源時,它會改為從指定的本機資料夾位置載入內容。 文件的來源也會是虛擬主機名稱。

此方法可讓您使用列舉來 CoreWebView2HostResourceAccessKind 指定跨原始來源存取。

由於目前的限制,使用虛擬主機名稱存取的媒體檔案載入速度可能會很慢。

使用虛擬主機名稱對應載入本機內容的考量

以原始來源為基礎的 DOM API

透過虛擬主機名稱對應載入的本機內容會產生具有 HTTP 或 HTTPS URL 以及對應來源的文件。 這表示需要原始來源 localStorage 的 Web API,例如 或 indexedDB 將可運作,以及屬於相同原始來源的其他文件將能夠使用儲存的資料。 如需詳細資訊,請參閱 MDN 上的 同源原則 。

需要安全內容的 DOM API

某些 Web API 僅限於安全的 HTTPS URL。 使用虛擬主機名稱對應可為您的本機內容提供 HTTPS URL。 這表示可以使用 API, navigator.mediaDevices.getUserMedia() 例如取得視訊或聲音、 navigator.geolocation.getCurrentPosition() 存取裝置位置或 Notification.requestPermission() 要求使用者顯示通知的權限。 如需詳細資訊,請參閱 MDN 上的 安全內容 。

動態內容

透過虛擬主機名稱對應載入本機內容時,您會將虛擬主機名稱對應至磁碟上包含靜態檔案的本機資料夾。 這表示無法動態修改此本機內容。 這與從 Web 伺服器載入文件不同,在 Web 伺服器中,每個回應都可以動態產生。

其他 Web 資源

透過虛擬主機名稱對應載入的本機內容具有支援相對 URL 解析的 HTTP 或 HTTPS URL。 這表示載入的文件可以參考其他 Web 資源,例如 CSS、指令碼或影像檔案,這些資源也透過虛擬主機名稱對應提供服務,但來源對應除外;請參閱下方具有 虛擬主機名稱對應的來源對應。

在 WebView2 程序中解決的其他 Web 資源

虛擬主機名稱 URL 會在 WebView2 處理序中解析。 這是比 WebResourceRequested更快的選項,可在主機應用程式處理序 UI 執行緒中解析。

具有虛擬主機名稱對應的來源對應

需要源映射來調試編譯內容的源代碼,包括:

  • 轉譯的 JavaScript,例如 TypeScript 或縮小的 JavaScript。
  • 編譯的 CSS,例如 SASS 或 SCSS。

WebView2 不會載入由使用虛擬主機名稱對應載入的內容所參考的來源對應。

例如,假設 WebView2 是透過虛擬主機名稱對應載入 main.js 。 如果參照作為main.js.map其來源對應,main.js.map將main.js不會自動載入。

若要搭配虛擬主機名稱對應使用來源對應,請在編譯內容期間產生內嵌來源對應。 內嵌來源對應會內嵌在對應的編譯檔案中。

使用虛擬主機名稱對應載入本機內容的 API

虛擬主機名稱對應範例

webView.CoreWebView2.SetVirtualHostNameToFolderMapping("demo",
         "C:\Github\Demos\demo-to-do", CoreWebView2HostResourceAccessKind.DenyCors);
webView.CoreWebView2.Navigate("https://demo/index.html");

透過處理 WebResourceRequested 事件載入本機內容

在 WebView2 控制項中託管本機內容的另一個方法是依賴 WebResourceRequested 事件。 當控制項嘗試載入資源時,就會觸發此事件。 您可以使用此事件來攔截要求並提供本機內容,如 網路要求的自訂管理中所述。

WebResourceRequested 可讓您根據每個要求自訂本機內容的行為。 這表示您可以決定要攔截哪些要求並提供您自己的內容,以及讓 WebView2 控制項正常處理哪些要求。 不過,自訂行為需要更多程式碼,例如虛擬主機對應,並且需要 HTTP 知識,才能建構適當的回應。

從 WebView2 的觀點來看,資源會透過網路提供,而 WebView2 會遵循應用程式在回應中設定的標頭。 由於每個要求都需要跨處理程序通訊和處理,因此使用 WebResourceRequested 事件也比其他方法慢。

自訂配置註冊

如果您想要使用自訂配置來提出產生事件的 WebResourceRequested Web 資源要求,請參閱 WebView2 API 概觀中的自訂配置註冊。

透過處理 WebResourceRequested 事件載入本機內容的考量

以原始來源為基礎的 DOM API

透過 WebResourceRequested 載入的本機內容會導致文件具有 HTTP 或 HTTPS URL 以及對應的來源。 這表示需要原始來源 localStorage 的 Web API,例如 或 indexedDB 將可運作,以及屬於相同原始來源的其他文件將能夠使用儲存的資料。 如需詳細資訊,請參閱 MDN 上的 同源原則 。

需要安全內容的 DOM API

某些 Web API 僅限於安全的 HTTPS URL。 使用 WebResourceRequested 可讓您以您自己的本機內容取代 HTTPS URL Web 資源要求。 這表示可以使用 API, navigator.mediaDevices.getUserMedia() 例如取得視訊或聲音、 navigator.geolocation.getCurrentPosition() 存取裝置位置或 Notification.requestPermission() 要求使用者顯示通知的權限。 如需詳細資訊,請參閱 MDN 上的 安全內容 。

動態內容

透過載 WebResourceRequested入本機內容時,您可以指定要在事件處理常式中載入的本機內容。 這意味著您可以在運行時控制內容,並且可以在需要時動態生成內容。

其他 Web 資源

WebResourceRequested 修改透過 HTTP 或 HTTPS URL 載入的內容,其支援相對 URL 解析。 這表示生成的文件可以參考其他 Web 資源,例如 CSS、指令碼或影像檔案,這些資源也會透過 WebResourceRequested提供,但來源對應除外;請參閱下方的事件 來源 WebResourceRequested 對應。

在 WebView2 程序中解決的其他 Web 資源

透過檔案 URL 或虛擬主機名稱對應載入內容時,會在 WebView2 處理序中執行解析。 不過,事件 WebResourceRequested 會在主應用程式處理序的 WebView2 UI 執行緒上引發,這可能會導致結果文件載入速度變慢。

  1. WebView2 會先暫停載入網頁,以等候事件傳送到您的主應用程式處理程序。
  2. 然後 WebView2 會等候您的 UI 執行緒可用。
  3. 然後 WebView2 會等候您的應用程式程式碼來處理事件。

這可能需要一些時間。 請務必將呼叫 AddWebResourceRequestedFilter 限制為僅限必須引發 WebResourceRequested 事件的 Web 資源。

來源對應與 WebResourceRequested 事件

需要源映射來調試編譯內容的源代碼,包括:

  • 轉譯的 JavaScript,例如 TypeScript 或縮小的 JavaScript。
  • 編譯的 CSS,例如 SASS 或 SCSS。

WebView2 不會載入由使用 WebResourceRequested 事件載入之內容所參考的來源對應。

例如,假設您在事件處理常式中WebResourceRequested載入main.js,方法是將 的屬性ResponseCoreWebView2WebResourceRequestedEventArgs設定為 。 如果 main.js 參照作為 main.js.map 其來源對應:

  • main.js.map 將不會自動載入。
  • WebResourceRequested事件處理常式不會再次呼叫來載入 main.js.map。

若要搭配 WebResourceRequested使用來源對應和 ,請使用下列其中一種方法:

  • 在編譯內容期間產生內嵌來源對應。 內嵌來源對應會內嵌在對應的編譯檔案中。

  • 或者,內嵌個別來源對應至事件處理常式中 WebResourceRequested 執行階段的內容。 只有當您的建置系統不支援內嵌來源對應時,才使用此方法。

透過處理 WebResourceRequested 事件載入本機內容的 API

處理 WebResourceRequested 事件的範例

// Reading of response content stream happens asynchronously, and WebView2 does not
// directly dispose the stream once it read.  Therefore, use the following stream
// class, which properly disposes when WebView2 has read all data.  For details, see
// [CoreWebView2 does not close stream content](https://github.com/MicrosoftEdge/WebView2Feedback/issues/2513).
class ManagedStream : Stream {
    public ManagedStream(Stream s)
    {
        s_ = s;
    }

    public override bool CanRead => s_.CanRead;

    public override bool CanSeek => s_.CanSeek;

    public override bool CanWrite => s_.CanWrite;

    public override long Length => s_.Length;

    public override long Position { get => s_.Position; set => s_.Position = value; }

    public override void Flush()
    {
        throw new NotImplementedException();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return s_.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int read = 0;
        try
        {
            read = s_.Read(buffer, offset, count);
            if (read == 0)
            {
                s_.Dispose();
            }
        }
        catch
        {
            s_.Dispose();
            throw;
        }
        return read;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

   private Stream s_;
}
webView.CoreWebView2.AddWebResourceRequestedFilter("https://demo/*",
                                                CoreWebView2WebResourceContext.All);
webView.CoreWebView2.WebResourceRequested += delegate (object sender,
                                     CoreWebView2WebResourceRequestedEventArgs args)
{
    string assetsFilePath = "C:\\Demo\\" +
                            args.Request.Uri.Substring("https://demo/*".Length - 1);
    try
    {
        FileStream fs = File.OpenRead(assetsFilePath);
        ManagedStream ms = new ManagedStream(fs);
        string headers = "";
        if (assetsFilePath.EndsWith(".html"))
        {
            headers = "Content-Type: text/html";
        }
        else if (assetsFilePath.EndsWith(".jpg"))
        {
            headers = "Content-Type: image/jpeg";
        } else if (assetsFilePath.EndsWith(".png"))
        {
            headers = "Content-Type: image/png";
        }
        else if (assetsFilePath.EndsWith(".css"))
        {
            headers = "Content-Type: text/css";
        }
        else if (assetsFilePath.EndsWith(".js"))
        {
            headers = "Content-Type: application/javascript";
        }

        args.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(
                                                            ms, 200, "OK", headers);
    }
    catch (Exception)
    {
        args.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(
                                                        null, 404, "Not found", "");
    }
};

另請參閱