From 3bcd285af21663d904a32f531129d5bbf6b0a0fd Mon Sep 17 00:00:00 2001 From: redth Date: Tue, 24 Feb 2026 20:50:24 -0500 Subject: [PATCH 1/6] Add UsePlatformHandler extension for custom BlazorWebView backends Adds IMauiBlazorWebViewBuilder.UsePlatformHandler() extension method that allows custom platform backends to replace the default BlazorWebViewHandler while reusing all shared service registrations from AddMauiBlazorWebView(). This enables scenarios like: builder.Services.AddMauiBlazorWebView() .UsePlatformHandler(); Fixes #34103 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MauiBlazorWebViewBuilderExtensions.cs | 31 +++++++++++++++++++ .../net-android/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 2 ++ .../net-maccatalyst/PublicAPI.Unshipped.txt | 2 ++ .../net-tizen/PublicAPI.Unshipped.txt | 2 ++ .../net-windows/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/net/PublicAPI.Unshipped.txt | 2 ++ .../Hosting/HostBuilderHandlerTests.cs | 15 +++++++++ 8 files changed, 58 insertions(+) create mode 100644 src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs diff --git a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs new file mode 100644 index 000000000000..e5b3e8f68a39 --- /dev/null +++ b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; + +namespace Microsoft.AspNetCore.Components.WebView.Maui +{ + /// + /// Extension methods for . + /// + public static class MauiBlazorWebViewBuilderExtensions + { + /// + /// Registers a custom handler for , replacing the default + /// registered by + /// . + /// This allows custom platform backends to provide their own BlazorWebView handler + /// while reusing all shared service registrations. + /// + /// The custom handler type to use for . + /// The . + /// The for chaining. + public static IMauiBlazorWebViewBuilder UsePlatformHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( + this IMauiBlazorWebViewBuilder builder) + where THandler : IElementHandler + { + builder.Services.ConfigureMauiHandlers(handlers => + handlers.AddHandler()); + return builder; + } + } +} diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt index 51e039fc88da..f326f0a40306 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! override Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler.ConnectHandler(Android.Webkit.WebView! platformView) -> void diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 7dc5c58110bf..3faccc736cba 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 7dc5c58110bf..3faccc736cba 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index 7dc5c58110bf..3faccc736cba 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 7dc5c58110bf..3faccc736cba 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt index 7dc5c58110bf..3faccc736cba 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs b/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs index ded85f07a562..7724085172c7 100644 --- a/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs +++ b/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs @@ -276,5 +276,20 @@ public void HostBuilderCannotResolveHandlerTypeForServiceRegisteredWithFactory() Type handlerType = mauiHandlersFactory.GetHandlerType(typeof(ViewStub)); Assert.Null(handlerType); } + + [Fact] + public void SecondConfigureMauiHandlersCallReplacesHandler() + { + var mauiApp = MauiApp.CreateBuilder() + .ConfigureMauiHandlers(handlers => handlers.AddHandler()) + .ConfigureMauiHandlers(handlers => handlers.AddHandler()) + .Build(); + + var mauiHandlersFactory = mauiApp.Services.GetRequiredService(); + + var handlerService = mauiHandlersFactory.GetHandler(typeof(ViewStub)); + Assert.NotNull(handlerService); + Assert.IsType(handlerService); + } } } \ No newline at end of file From c6b040e36e2cf5604f5bbb433a3e676a9e739b6e Mon Sep 17 00:00:00 2001 From: redth Date: Mon, 9 Mar 2026 13:44:17 -0400 Subject: [PATCH 2/6] Apply review feedback: tighten constraint to IViewHandler and fix test stub type - Constrain UsePlatformHandler to IViewHandler instead of IElementHandler since it registers for IBlazorWebView (an IView) - Replace AlternateButtonHandlerStub with AlternateViewHandlerStub in test to match the IViewStub registration type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Maui/MauiBlazorWebViewBuilderExtensions.cs | 2 +- .../tests/UnitTests/Hosting/HostBuilderHandlerTests.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs index e5b3e8f68a39..53f1bfcaeaaf 100644 --- a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs +++ b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs @@ -21,7 +21,7 @@ public static class MauiBlazorWebViewBuilderExtensions /// The for chaining. public static IMauiBlazorWebViewBuilder UsePlatformHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( this IMauiBlazorWebViewBuilder builder) - where THandler : IElementHandler + where THandler : IViewHandler { builder.Services.ConfigureMauiHandlers(handlers => handlers.AddHandler()); diff --git a/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs b/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs index 7724085172c7..5788bf15ccf5 100644 --- a/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs +++ b/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs @@ -282,14 +282,18 @@ public void SecondConfigureMauiHandlersCallReplacesHandler() { var mauiApp = MauiApp.CreateBuilder() .ConfigureMauiHandlers(handlers => handlers.AddHandler()) - .ConfigureMauiHandlers(handlers => handlers.AddHandler()) + .ConfigureMauiHandlers(handlers => handlers.AddHandler()) .Build(); var mauiHandlersFactory = mauiApp.Services.GetRequiredService(); var handlerService = mauiHandlersFactory.GetHandler(typeof(ViewStub)); Assert.NotNull(handlerService); - Assert.IsType(handlerService); + Assert.IsType(handlerService); + } + + class AlternateViewHandlerStub : ViewHandlerStub + { } } } \ No newline at end of file From 1da8458b71e2aebd46467e83506d373bca69c429 Mon Sep 17 00:00:00 2001 From: redth Date: Wed, 1 Apr 2026 19:15:48 -0400 Subject: [PATCH 3/6] Address review feedback: add new() constraint and factory-based overload - Add new() constraint to UsePlatformHandler() to make the public parameterless constructor requirement explicit at compile time - Add factory-based UsePlatformHandler(Func) overload for handlers that need dependency injection - Add ArgumentNullException.ThrowIfNull guard on factory parameter - Update all 6 TFM PublicAPI.Unshipped.txt files with new overload - Add FactoryBasedHandlerRegistrationReplacesHandler test Addresses review comment about Activator.CreateInstance requiring parameterless constructors and MauiBot recommendation for DI-friendly handler activation path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MauiBlazorWebViewBuilderExtensions.cs | 28 +++++++++++++++++-- .../net-android/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 1 + .../net-maccatalyst/PublicAPI.Unshipped.txt | 1 + .../net-tizen/PublicAPI.Unshipped.txt | 1 + .../net-windows/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/net/PublicAPI.Unshipped.txt | 1 + .../Hosting/HostBuilderHandlerTests.cs | 15 ++++++++++ 8 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs index 53f1bfcaeaaf..83017dea2e8c 100644 --- a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs +++ b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Maui; using Microsoft.Maui.Hosting; @@ -16,16 +17,37 @@ public static class MauiBlazorWebViewBuilderExtensions /// This allows custom platform backends to provide their own BlazorWebView handler /// while reusing all shared service registrations. /// - /// The custom handler type to use for . + /// The custom handler type to use for . + /// Must have a public parameterless constructor. /// The . /// The for chaining. public static IMauiBlazorWebViewBuilder UsePlatformHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( this IMauiBlazorWebViewBuilder builder) - where THandler : IViewHandler + where THandler : IViewHandler, new() { builder.Services.ConfigureMauiHandlers(handlers => handlers.AddHandler()); return builder; } + + /// + /// Registers a custom handler for using a factory method, + /// replacing the default registered by + /// . + /// This overload supports handlers that require dependency injection or lack a public + /// parameterless constructor. + /// + /// The . + /// A factory function that creates the handler instance using the . + /// The for chaining. + public static IMauiBlazorWebViewBuilder UsePlatformHandler( + this IMauiBlazorWebViewBuilder builder, + Func factory) + { + ArgumentNullException.ThrowIfNull(factory); + builder.Services.ConfigureMauiHandlers(handlers => + handlers.AddHandler(factory)); + return builder; + } } } diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt index f326f0a40306..343cd6cc4b46 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! override Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler.ConnectHandler(Android.Webkit.WebView! platformView) -> void diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 3faccc736cba..39d3b68fff47 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 3faccc736cba..39d3b68fff47 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index 3faccc736cba..39d3b68fff47 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 3faccc736cba..39d3b68fff47 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt index 3faccc736cba..39d3b68fff47 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs b/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs index 5788bf15ccf5..f2b2d570bbd0 100644 --- a/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs +++ b/src/Core/tests/UnitTests/Hosting/HostBuilderHandlerTests.cs @@ -292,6 +292,21 @@ public void SecondConfigureMauiHandlersCallReplacesHandler() Assert.IsType(handlerService); } + [Fact] + public void FactoryBasedHandlerRegistrationReplacesHandler() + { + var mauiApp = MauiApp.CreateBuilder() + .ConfigureMauiHandlers(handlers => handlers.AddHandler()) + .ConfigureMauiHandlers(handlers => handlers.AddHandler(_ => new AlternateViewHandlerStub())) + .Build(); + + var mauiHandlersFactory = mauiApp.Services.GetRequiredService(); + + var handlerService = mauiHandlersFactory.GetHandler(typeof(ViewStub)); + Assert.NotNull(handlerService); + Assert.IsType(handlerService); + } + class AlternateViewHandlerStub : ViewHandlerStub { } From f7e68c12d63bc86e341288dc90f81b3d4ed16d71 Mon Sep 17 00:00:00 2001 From: redth Date: Wed, 1 Apr 2026 19:28:06 -0400 Subject: [PATCH 4/6] Add builder null checks and tighten factory overload to IViewHandler - Add ArgumentNullException.ThrowIfNull(builder) to both overloads for consistent null validation - Change factory overload parameter from Func to Func for type safety consistency with the generic overload's IViewHandler constraint - Update PublicAPI files to reflect the tighter factory signature Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Maui/MauiBlazorWebViewBuilderExtensions.cs | 4 +++- .../src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt | 2 +- .../src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt | 2 +- .../Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt | 2 +- .../src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt | 2 +- .../src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt | 2 +- .../src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs index 83017dea2e8c..c61fd1df3370 100644 --- a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs +++ b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs @@ -25,6 +25,7 @@ public static class MauiBlazorWebViewBuilderExtensions this IMauiBlazorWebViewBuilder builder) where THandler : IViewHandler, new() { + ArgumentNullException.ThrowIfNull(builder); builder.Services.ConfigureMauiHandlers(handlers => handlers.AddHandler()); return builder; @@ -42,8 +43,9 @@ public static class MauiBlazorWebViewBuilderExtensions /// The for chaining. public static IMauiBlazorWebViewBuilder UsePlatformHandler( this IMauiBlazorWebViewBuilder builder, - Func factory) + Func factory) { + ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(factory); builder.Services.ConfigureMauiHandlers(handlers => handlers.AddHandler(factory)); diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt index 343cd6cc4b46..038b7546d70e 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1,5 +1,5 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions -static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! override Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler.ConnectHandler(Android.Webkit.WebView! platformView) -> void diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 39d3b68fff47..79428f930adf 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -1,4 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions -static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 39d3b68fff47..79428f930adf 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -1,4 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions -static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index 39d3b68fff47..79428f930adf 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -1,4 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions -static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 39d3b68fff47..79428f930adf 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -1,4 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions -static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! diff --git a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt index 39d3b68fff47..79428f930adf 100644 --- a/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt @@ -1,4 +1,4 @@ #nullable enable Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions -static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! +static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder, System.Func! factory) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! static Microsoft.AspNetCore.Components.WebView.Maui.MauiBlazorWebViewBuilderExtensions.UsePlatformHandler(this Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! builder) -> Microsoft.AspNetCore.Components.WebView.Maui.IMauiBlazorWebViewBuilder! From a41994f381f2fb5ce2827dd5e1c904e28298b2aa Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:50:56 +0200 Subject: [PATCH 5/6] Address multi-model code-review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three reviewers (Claude Opus 4.7-xhigh, Claude Sonnet 4.6, GPT-5.5) flagged two shared concerns on the new UsePlatformHandler API: 1. Tests don't actually call UsePlatformHandler. The two existing HostBuilderHandlerTests prove the underlying ConfigureMauiHandlers replacement mechanism with stub types but never exercise the new public BlazorWebView API surface. A typo in MauiBlazorWebViewBuilderExtensions (wrong type argument, wrong service type, missing closure capture) would not be caught. 2. The XML doc on the factory overload claims it 'supports handlers that require dependency injection,' but the IServiceProvider passed by MauiFactory to the factory delegate is the MauiHandlersFactory itself — not the application's root IServiceProvider. It can resolve handler-collection services only. This commit: * Adds two direct device-test Facts in BlazorWebViewTests.Services.cs that call AddMauiBlazorWebView().UsePlatformHandler() / .UsePlatformHandler(factory) and assert the IBlazorWebView handler resolves to the custom type. The stub inherits BlazorWebViewHandler so it satisfies IViewHandler/new() on every device-test target without reimplementing the handler surface. * Tightens the XML docs on both overloads with a block explaining the last-registration-wins ordering rule (call after AddMauiBlazorWebView, and call last when downstream libraries may re-invoke AddMauiBlazorWebView). * Clarifies the factory-overload contract so readers don't expect arbitrary app-level DI to flow through the factory delegate's IServiceProvider. No public API surface changes — only XML docs and tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MauiBlazorWebViewBuilderExtensions.cs | 26 ++++++++-- .../Elements/BlazorWebViewTests.Services.cs | 52 +++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs index c61fd1df3370..70de94bf50f7 100644 --- a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs +++ b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs @@ -17,6 +17,14 @@ public static class MauiBlazorWebViewBuilderExtensions /// This allows custom platform backends to provide their own BlazorWebView handler /// while reusing all shared service registrations. /// + /// + /// Replacement is "last-registration-wins" through the underlying MAUI handler collection. + /// Call this method after AddMauiBlazorWebView() so the custom handler + /// overrides the default registration. If a downstream library calls + /// AddMauiBlazorWebView() again later in the pipeline, that subsequent default + /// registration will silently re-override this custom handler — call this method last, + /// after every other library's MAUI Blazor configuration, when composing multiple sources. + /// /// The custom handler type to use for . /// Must have a public parameterless constructor. /// The . @@ -35,11 +43,23 @@ public static class MauiBlazorWebViewBuilderExtensions /// Registers a custom handler for using a factory method, /// replacing the default registered by /// . - /// This overload supports handlers that require dependency injection or lack a public - /// parameterless constructor. + /// Use this overload for handlers that lack a public parameterless constructor or that + /// need to pull dependencies from the MAUI handler service container at construction time. /// + /// + /// The passed to is the MAUI + /// handler factory's service provider, not the application's root . + /// It can resolve services that were registered on the handler collection (via + /// ConfigureMauiHandlers); it cannot resolve arbitrary services from the app's + /// . The same call-ordering rule as + /// applies — call this + /// method after AddMauiBlazorWebView() (and after any later re-invocations from + /// downstream libraries) so the custom handler is the last registration to win. + /// /// The . - /// A factory function that creates the handler instance using the . + /// A factory function that creates the handler instance. + /// The argument is the MAUI handler factory's service provider + /// (see remarks). /// The for chaining. public static IMauiBlazorWebViewBuilder UsePlatformHandler( this IMauiBlazorWebViewBuilder builder, diff --git a/src/BlazorWebView/tests/DeviceTests/Elements/BlazorWebViewTests.Services.cs b/src/BlazorWebView/tests/DeviceTests/Elements/BlazorWebViewTests.Services.cs index 9a4c8fd9132a..b29a23d27192 100644 --- a/src/BlazorWebView/tests/DeviceTests/Elements/BlazorWebViewTests.Services.cs +++ b/src/BlazorWebView/tests/DeviceTests/Elements/BlazorWebViewTests.Services.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components.WebView.Maui; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.Hosting; using Microsoft.Maui.MauiBlazorWebView.DeviceTests.Components; using WebViewAppShared; using Xunit; @@ -119,4 +120,55 @@ await Assert.ThrowsAsync(async () => }); }); } + + [Fact] + public void UsePlatformHandlerGenericReplacesDefaultBlazorWebViewHandler() + { + // Verifies that the public UsePlatformHandler() extension on IMauiBlazorWebViewBuilder + // actually replaces the default BlazorWebViewHandler registered by AddMauiBlazorWebView() + // for the IBlazorWebView service type. The two HostBuilderHandlerTests in Core verify the + // underlying ConfigureMauiHandlers replacement mechanism with stub types; this test exercises + // the new public API surface end-to-end with the real BlazorWebView/IBlazorWebView types. + var builder = MauiApp.CreateBuilder(); + builder.Services.AddMauiBlazorWebView() + .UsePlatformHandler(); + using var app = builder.Build(); + + var handlersFactory = app.Services.GetRequiredService(); + Assert.Equal(typeof(CustomBlazorWebViewHandlerStub), handlersFactory.GetHandlerType(typeof(BlazorWebView))); + } + + [Fact] + public void UsePlatformHandlerFactoryReplacesDefaultBlazorWebViewHandler() + { + // Companion to UsePlatformHandlerGenericReplacesDefaultBlazorWebViewHandler — verifies the + // factory overload (Func) also replaces the default handler. + // The factory overload registers a different ServiceDescriptor shape (ImplementationFactory + // rather than ImplementationType), so GetHandlerType returns null here; we resolve through + // GetHandler instead and assert the produced instance type. + var builder = MauiApp.CreateBuilder(); + var factoryWasCalled = false; + builder.Services.AddMauiBlazorWebView() + .UsePlatformHandler(_ => + { + factoryWasCalled = true; + return new CustomBlazorWebViewHandlerStub(); + }); + using var app = builder.Build(); + + var handlersFactory = app.Services.GetRequiredService(); + var handler = handlersFactory.GetHandler(typeof(BlazorWebView)); + + Assert.True(factoryWasCalled, "Factory delegate should have been invoked when the handler was resolved."); + Assert.IsType(handler); + } + + private class CustomBlazorWebViewHandlerStub : BlazorWebViewHandler + { + // Marker subclass used only to prove that UsePlatformHandler replaced the default + // BlazorWebViewHandler registration. Inheriting from BlazorWebViewHandler keeps the + // IViewHandler contract honored on every device-test target framework without forcing + // us to reimplement the full handler surface. + public CustomBlazorWebViewHandlerStub() { } + } } From f146082ab0e5c9b586e13147f97a681568d7591f Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:55:48 +0200 Subject: [PATCH 6/6] Fix unresolved cref to IServiceCollection in XML doc GPT-5.5 reviewer caught a build-breaking CS1574 introduced by the previous commit's block: the file does not 'using Microsoft.Extensions.DependencyInjection;', so '' fails to resolve and breaks docs compilation. Fully qualify the cref to match the file's existing pattern (see the cref to BlazorWebViewServiceCollectionExtensions.AddMauiBlazorWebView elsewhere in this file, which uses 'M:Microsoft.Extensions.DependencyInjection.BlazorWebViewServiceCollectionExtensions...'). Verified: 'dotnet build src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj -f net10.0' now succeeds with 0 warnings, 0 errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Maui/MauiBlazorWebViewBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs index 70de94bf50f7..ad9553b07fc5 100644 --- a/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs +++ b/src/BlazorWebView/src/Maui/MauiBlazorWebViewBuilderExtensions.cs @@ -51,7 +51,7 @@ public static class MauiBlazorWebViewBuilderExtensions /// handler factory's service provider, not the application's root . /// It can resolve services that were registered on the handler collection (via /// ConfigureMauiHandlers); it cannot resolve arbitrary services from the app's - /// . The same call-ordering rule as + /// . The same call-ordering rule as /// applies — call this /// method after AddMauiBlazorWebView() (and after any later re-invocations from /// downstream libraries) so the custom handler is the last registration to win.