WebView2 in WebUI3: how to disable CORS for fetch requests?

Viewed 163

I have a simple web app I would like to run in a WinUI app that can fetch external HTML content:

    private async fetchHtml() {
        const url = document.querySelector<HTMLInputElement>("#txt-url")!.value;
        const html = await fetch(url).then(r => r.text());
        document.querySelector<HTMLTextAreaElement>("#txt-html")!.value = html;
    }

Obviously this would hit a CORS error. I want to disable it but cannot find any way for WinUI3:

  • WebView2 in WinUI does not have EnsureCoreWebView2Async overload that takes CoreWebView2EnvironmentOptions so this answer does not help.

  • The URL is external URL so SetVirtualHostNameToFolderMapping suggested by this answer does not help as well.

  • I tried injecting my own CORS header but it does not work on fetch (WebResourceResponseReceived event is not fired only on fetch requests):

c.WebResourceResponseReceived += (_, e) =>
{
    var origin = e.Request.Headers.FirstOrDefault(q => q.Key == "Origin").Value;
    if (!string.IsNullOrEmpty(origin))
    {
        e.Response.Headers.AppendHeader("Access-Control-Allow-Origin",
            new Uri(e.Request.Uri).GetLeftPart(UriPartial.Authority));
        e.Response.Headers.AppendHeader("Access-Control-Allow-Methods", "*");
        e.Response.Headers.AppendHeader("Access-Control-Allow-Headers", "*");
    }
};
1 Answers

So it turns out you actually need to handle proxying the request in the WebResourceRequested event. WebResourceResponseReceived is triggered after the browser handles the request.

To proxy the request manually, you need to first set up a web resource requested filter.

// CoreWebView2WebResourceContext.XmlHttpRequest is triggered
// by fetches for some reason. This filter will listen for any URI.
this.webView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.XmlHttpRequest);

Then you can set up a listener just like for WebResourceResponseReceived

this.webView.CoreWebView2.WebResourceRequested += this.CoreWebView2_WebResourceRequested;

So to proxy the request, you need to set up an HttpClient and handle the requests manually.

For example, this will proxy all requests, assuming httpClient is defined as an HttpClient:

private void CoreWebView2_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs e)
{
    HttpRequestMessage request = new(new HttpMethod(e.Request.Method), e.Request.Uri)
    {
        Content = new StreamContent(e.Request.Content)
    };

    foreach (KeyValuePair<string, string> header in e.Request.Headers)
    {
        // Copy base headers
        request.Headers.TryAddWithoutValidation(header.Key, header.Value);

        // Copy content headers
        request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
    }

    // Make the request
    HttpResponseMessage response = httpClient.Send(request);

    // Custom headers
    response.Headers.Add("Access-Control-Allow-Origin", "*");

    // Webview2 Response expecting headers in a string with following format:
    //   key: value
    //   header2: value
    List<string> headers = response.Headers.Select(h => $"{h.Key}: {string.Join(",", h.Value)}").ToList();

    // Create Webview2 response
    CoreWebView2WebResourceResponse webView2WebResourceResponse = this.webView.CoreWebView2.Environment.CreateWebResourceResponse(
        response.Content.ReadAsStream(),
        (int)response.StatusCode,
        response.ReasonPhrase,
        string.Join('\n', headers));

    e.Response = webView2WebResourceResponse;
}

NOTE

Should not use async methods when building out a response object. The COM layer does not respect threading.

Related