[net11.0] Trimmable element handlers#29952
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR transitions built-in controls from DI registration to using [ElementHandler] attributes, introduces IElementHandlerWithAndroidContext<T> for Android renderers, and adds a new MSBuild property to toggle CSS support.
- Apply
[ElementHandler]to core controls and cells instead of DI - Introduce
IElementHandlerWithAndroidContext<T>andCreateHandler(Context)on Android renderers - Add
MauiCssEnabledMSBuild property and corresponding runtime host configuration
Reviewed Changes
Copilot reviewed 120 out of 120 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Controls/src/Core/ContentPage/ContentPage.Mapper.cs | Added static ctor calling RemapForControls and VisualElement.RemapIfNeeded |
| src/Controls/src/Core/Compatibility/Handlers/TableView/Android/TableViewRenderer.cs | Implemented IElementHandlerWithAndroidContext<TableViewRenderer> and CreateHandler |
| src/Controls/src/Core/Compatibility/Handlers/ListView/Android/ListViewRenderer.cs | Implemented IElementHandlerWithAndroidContext<ListViewRenderer> and CreateHandler |
| src/Controls/src/Core/Compatibility/Handlers/Android/FrameRenderer.cs | Implemented IElementHandlerWithAndroidContext<FrameRenderer> and CreateHandler |
| src/Controls/src/Core/Cells/*.cs | Added [ElementHandler<...>] attributes for compatibility cell renderers |
| src/Controls/src/Core/Button/Button.cs | Added [ElementHandler<ButtonHandler>] |
| src/Controls/src/Core//.Mapper.cs | Added static ctors calling RemapForControls / RemapIfNeeded |
| src/Controls/src/Build.Tasks/nuget/.../Microsoft.Maui.Controls.targets | Added MauiCssEnabled property and runtime host configuration option |
| src/Compatibility/Core/src/{iOS,Android}/Platform.cs | Updated GetHandler calls to pass MauiContext |
Comments suppressed due to low confidence (1)
src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets:240
- Added the
MauiCssEnabledbuild property but no corresponding public documentation under/docs/; please update the XML docs to describe this new feature.
<MauiCssEnabled Condition="'$(MauiCssEnabled)' == '' and '@(MauiCss->Count())' == '0'">false</MauiCssEnabled>
| { | ||
| #pragma warning disable CS0618 // Type or member is obsolete | ||
| public class TableViewRenderer : ViewRenderer<TableView, AListView> | ||
| public class TableViewRenderer : ViewRenderer<TableView, AListView>, IElementHandlerWithAndroidContext<TableViewRenderer> |
There was a problem hiding this comment.
Implementing IElementHandlerWithAndroidContext introduces a new public interface on this renderer, which is a breaking API change; please assess version impact.
| { | ||
| #pragma warning disable CS0618 // Type or member is obsolete | ||
| public class ListViewRenderer : ViewRenderer<ListView, AListView> | ||
| public class ListViewRenderer : ViewRenderer<ListView, AListView>, IElementHandlerWithAndroidContext<ListViewRenderer> |
There was a problem hiding this comment.
Implementing IElementHandlerWithAndroidContext introduces a new public interface on this renderer, which is a breaking API change; please assess version impact.
| { | ||
| [Obsolete("Frame is obsolete as of .NET 9. Please use Border instead.")] | ||
| public class FrameRenderer : CardView, IPlatformViewHandler | ||
| public class FrameRenderer : CardView, IPlatformViewHandler, IElementHandlerWithAndroidContext<FrameRenderer> |
There was a problem hiding this comment.
Implementing IElementHandlerWithAndroidContext introduces a new public interface on this renderer, which is a breaking API change; please assess version impact.
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
33afe18 to
c49e57e
Compare
|
@copilot there are merge conflicts in this branch. please resolve them. When you're done, write summary of what this PR does and what functionality is missing to fully convert the "old" way of implementing handlers to the "new" way. |
|
@simonrozsival I've opened a new pull request, #32861, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@copilot oops, try now! |
|
@simonrozsival I've opened a new pull request, #32862, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@copilot there are several errors popping up:
Fix them and make sure the product can be built. |
|
@simonrozsival I've opened a new pull request, #32933, to work on those changes. Once the pull request is ready, I'll request review from you. |
1386e4a to
81acaeb
Compare
dbd26ce to
4dbc042
Compare
|
/rebase |
|
@copilot this PR needs a proper rebase with conflict resolution. open a PR which addresses this issue. make sure it is up to date with the base net11.0 branch. |
|
@simonrozsival I've opened a new pull request, #34008, to work on those changes. Once the pull request is ready, I'll request review from you. |
4dbc042 to
b8a94ba
Compare
c1ce0c9 to
13753db
Compare
f5c8447 to
f097792
Compare
|
It's not clear to me @simonrozsival how one can create their own Edit: yes it seems DI win, thanks! So this is basically a feature for library authors to make their handlers trimmable. |
|
@albyrock87 exactly. If you don't use webview in your app, there should be no webview related code in the app. This change should be as transparent as possible. Any custom handler registered via DI should still work. |
f097792 to
6695f72
Compare
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 29952Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 29952" |
Squashed for clean rebase onto net11.0.
6695f72 to
d3fb530
Compare
Handlers resolved via [ElementHandler] attribute use Activator.CreateInstance(), not ActivatorUtilities. Co-authored-by: Copilot <[email protected]>
This is a follow-up to #28357
Summary
All built-in controls now use
[ElementHandler]attributes instead of DI registration for handler resolution. This removes the need forAppHostBuilderExtensions.AddControlsHandlers()and makes handler associations visible at the type level, improving trimmability.Key changes
[ElementHandler]attributeElementHandlerAttributeon view types (e.g.,[ElementHandler(typeof(ButtonHandler))]) declaratively associates a view with its handlerInherited = false— each concrete type that needs a different handler must declare its own attributeBaseTypechain at resolution time, so derived types without their own attribute inherit the nearest ancestor's handlerHandler resolution in
MauiHandlersFactoryBoth
GetHandler(Type)andGetHandlerType(Type)follow the same 4-step resolution order:typeof(Button)) is registered viaAddHandler<Button, MyButtonHandler>(), that wins. DI-registered handlers are instantiated throughMauiFactory.GetService().[ElementHandler]attribute — walk the type'sBaseTypechain looking for the attribute. Attribute-based handlers are instantiated viaActivator.CreateInstance().Button→IButton) usingRegisteredHandlerServiceTypeSet, then look up the interface in DI. This is the path for third-party/custom handler overrides registered viaAddHandler<IButton, ...>().IContentViewfallback — if the type implementsIContentView, returnContentViewHandler.GetHandler()is the primary API — it returns an instantiatedIElementHandler.GetHandlerType()returns only theTypeand is used by code that needs to compare handler types without instantiating them (e.g., Compatibility layer cell renderers).ElementExtensions.ToHandler()/SetHandler()These methods call
GetHandler()directly. When a handler requires constructor parameters (no parameterless constructor),ToHandler()catches theMissingMethodExceptionand falls back toActivatorUtilities.CreateInstance()with DI injection. This fallback path is cached per-type in aHashSet<Type>to avoid repeated exception throwing.Mapper remapping via static constructors
RemappingHelper.EnsureBaseTypeRemapped(), which accesses as_forceStaticConstructorfield on the parent type to force the runtime to run the parent's static constructor firstRemappingDebugHelper.AssertBaseClassForRemapping(DEBUG-only) validates that no intermediate type in the hierarchy is accidentally skippedCleanup
IElementHandlerWithAndroidContextinterface andElementHandlerWithAndroidContextAttributeRemapForControls(this MauiAppBuilder)extension method and oldRemapForControls()/RemapIfNeeded()patternIMauiHandlersFactory.GetHandler()to its original signature (GetHandler(Type)/GetHandler<T>()) — no longer requiresIMauiContextparameterMauiContext/HandlersContextStubdeclarations from tests and benchmarks