Supporting custom protocols in WebView2

 
 
  • Gérald Barré

WebView2 is a new web browser control for Windows desktop applications. It is based on the Chromium engine and is a replacement for the old WebBrowser control. A browser mostly handles HTTP requests, but it is also possible to handle custom protocols. Custom protocols are used by some applications to open a specific page. For example, the mailto: protocol is used to open the default email client. When accessing a custom protocol, the browser handles the request and opens the corresponding application. In the case of the WebView2 control, you can handle the request directly instead of opening the application. In this post, I describe how to support custom protocols in WebView2.

First, you need to create a new WPF application and add the Microsoft.Web.WebView2 package:

Shell
dotnet new wpf
dotnet add package Microsoft.Web.WebView2

Then, you can add a WebView2 control to the main window:

MainWindow.xaml (XAML)
<Window x:Class="DemoWebView2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DemoWebView2"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <wv2:WebView2 Name="webView" />
    </Grid>
</Window>

To register the protocol, you need to initialize the WebView2 control with a custom environment:

MainWindow.xaml.cs (C#)
public partial class MainWindow : Window
{
    public MainWindow() => InitializeComponent();

    protected override async void OnSourceInitialized(EventArgs e)
    {
        // Register the custom protocol
        var customSchemeRegistrations = new List<CoreWebView2CustomSchemeRegistration>
        {
            new CoreWebView2CustomSchemeRegistration("meziantou")
            {
                TreatAsSecure = true, // avoid mixed content error
                HasAuthorityComponent = false
            }
        };

        var options = new CoreWebView2EnvironmentOptions(
            additionalBrowserArguments: null,
            language: null,
            targetCompatibleBrowserVersion: null,
            allowSingleSignOnUsingOSPrimaryAccount: false,
            customSchemeRegistrations: customSchemeRegistrations);

        var environment = await CoreWebView2Environment.CreateAsync(
            userDataFolder: null,
            options: options);

        await webView.EnsureCoreWebView2Async(environment);

        // Intercept all requests on the protocol
        webView.CoreWebView2.AddWebResourceRequestedFilter("meziantou://*", CoreWebView2WebResourceContext.All);
        webView.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;

        // Load a page provided by the custom protocol
        webView.Source = new Uri("meziantou://home");
    }

    private async void CoreWebView2_WebResourceRequested(object? sender, CoreWebView2WebResourceRequestedEventArgs e)
    {
        var webView = (CoreWebView2)sender!;
        if (e.Request.Method == "GET" && e.Request.Uri == "meziantou://home/")
        {
            // If you want to return a stream that needs to be disposed, you need to wrap it in a ManagedStream.
            // This stream will dispose the underlying stream when it is read completely.
            // You can find the source code here:
            // https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/working-with-local-content?tabs=dotnetcsharp&WT.mc_id=DT-MVP-5003978#example-of-handling-the-webresourcerequested-event
            e.Response = webView.Environment.CreateWebResourceResponse(
                Content: new MemoryStream(Encoding.UTF8.GetBytes("Hello world! <a href='meziantou://page2/'>navigate to page2</a>")),
                StatusCode: 200,
                ReasonPhrase: "Ok",
                Headers: "Content-Type: text/html");
        }
        else if (e.Request.Method == "GET" && e.Request.Uri == "meziantou://page2/")
        {
            // If you want to return the response asynchronously, you need to use the GetDeferral method.
            using (e.GetDeferral())
            {
                // Simulate a long operation
                await Task.Delay(100);

                // Create the response
                e.Response = webView.Environment.CreateWebResourceResponse(
                    Content: new MemoryStream(Encoding.UTF8.GetBytes("This is page 2!")),
                    StatusCode: 200,
                    ReasonPhrase: "Ok",
                    Headers: "Content-Type: text/html");
            }
        }
    }
}

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub