feat: add AOT-safe ViewLocator with source-generated view dispatch#15
feat: add AOT-safe ViewLocator with source-generated view dispatch#15glennawatson merged 2 commits intomainfrom
Conversation
## New Features - Add IViewLocator, DefaultViewLocator, ViewLocator, and ViewMappingBuilder runtime types supporting three-tier view resolution: generated dispatch → explicit mappings → service locator fallback. - Add source generator that scans IViewFor<T> implementations at compile time and generates a type-switch dispatch function for AOT-safe view resolution. - Add ViewRegistrationExtractor and ViewRegistrationInfo model for extracting view/view-model pairs from the compilation. - Integrate ConfigureViewLocator into the builder pattern via IReactiveUIBindingBuilder, ReactiveUIBindingBuilder, and BuilderMixins. ## Tests - Add 7 snapshot tests for ViewLocatorDispatchGenerator covering single/multiple views, abstract exclusion, private/missing constructors, deduplication, and non-IViewFor classes. - Add 31 runtime tests for DefaultViewLocator, ViewLocator, ViewMappingBuilder, ViewLocatorNotFoundException, and builder integration. - Add TestExecutor infrastructure following ReactiveUI's executor pattern for clean test isolation.
There was a problem hiding this comment.
Pull request overview
This PR adds an AOT-safe ViewLocator system to ReactiveUI.Binding, replacing the reflection-based MakeGenericType approach with a source-generated type-switch dispatch. The implementation follows a three-tier resolution strategy: compile-time generated dispatch, explicit runtime Map<TVM, TView>() mappings, and Splat service locator fallback.
Changes:
- Adds
DefaultViewLocator,ViewLocator,IViewLocator,ViewMappingBuilder, andViewLocatorNotFoundExceptionto the runtime library, with builder integration viaConfigureViewLocatoron bothIReactiveUIBindingBuilderandIAppBuilderextension mixins. - Adds a new source generator pipeline (Pipeline C) in
ViewLocatorDispatchGeneratorthat scansIViewFor<T>implementations at compile time and generates a type-switch dispatch method inViewDispatch.g.cs. - Adds comprehensive tests: 7 snapshot tests for the generator and 31+ runtime tests for
DefaultViewLocator,ViewLocator,ViewMappingBuilder, exception handling, and builder integration, using the project'sTestExecutorinfrastructure.
Reviewed changes
Copilot reviewed 43 out of 43 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/ReactiveUI.Binding/View/DefaultViewLocator.cs |
Core three-tier view resolution implementation |
src/ReactiveUI.Binding/View/ViewLocator.cs |
Static accessor for the current IViewLocator |
src/ReactiveUI.Binding/View/ViewLocatorNotFoundException.cs |
Custom exception for missing IViewLocator |
src/ReactiveUI.Binding/View/ViewMappingBuilder.cs |
Fluent builder for registering view-to-VM mappings |
src/ReactiveUI.Binding/Interfaces/IViewLocator.cs |
IViewLocator interface with generic and non-generic overloads |
src/ReactiveUI.Binding/Builder/IReactiveUIBindingBuilder.cs |
Adds ConfigureViewLocator to the builder interface |
src/ReactiveUI.Binding/Builder/ReactiveUIBindingBuilder.cs |
Implements ConfigureViewLocator and registers default IViewLocator in WithCoreServices |
src/ReactiveUI.Binding/Mixins/BuilderMixins.cs |
Adds ConfigureViewLocator extension for IAppBuilder |
src/ReactiveUI.Binding.SourceGenerators/Generators/ViewLocatorDispatchGenerator.cs |
Source generator that produces ViewDispatch.g.cs |
src/ReactiveUI.Binding.SourceGenerators/Helpers/ViewRegistrationExtractor.cs |
Extracts IViewFor implementations from syntax |
src/ReactiveUI.Binding.SourceGenerators/Models/ViewRegistrationInfo.cs |
POCO model for detected view registrations |
src/ReactiveUI.Binding.SourceGenerators/Constants.cs |
Adds IViewForGenericMetadataName constant |
src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs |
Registers Pipeline C in the generator |
src/tests/.../ViewLocatorDispatchGeneratorTests.cs |
7 snapshot tests for the generator |
src/tests/.../DefaultViewLocatorTests.cs |
Runtime tests for DefaultViewLocator |
src/tests/.../ViewLocatorTests.cs |
Tests for ViewLocator static accessor |
src/tests/.../ViewLocatorNotFoundExceptionTests.cs |
Tests for exception constructors |
src/tests/.../ViewMappingBuilderTests.cs |
Tests for ViewMappingBuilder |
src/tests/.../Builder/ReactiveUIBindingBuilderTests.cs |
Tests for ConfigureViewLocator builder integration |
src/tests/.../Builder/BuilderMixinsTests.cs |
Tests for ConfigureViewLocator mixin |
src/tests/.../TestExecutors/* |
Test executor infrastructure for builder state isolation |
src/tests/.../Helpers/TestHelper.cs |
Adds VDG abbreviation for snapshot tests |
src/tests/.../*.verified.cs |
Verified snapshot files for generator output |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Update BindingGenerator XML doc to mention all three pipelines (A, B, C) - Make DefaultViewLocator _lock instance-scoped instead of static since it only guards the per-instance _mappings dictionary
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #15 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 121 125 +4
Lines 2014 2116 +102
Branches 375 395 +20
==========================================
+ Hits 2014 2116 +102 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
This PR ports ReactiveUI's ViewLocator system into ReactiveUI.Binding, replacing the reflection-based
MakeGenericTypeapproach with a fully AOT-safe, source-generated alternative.In ReactiveUI,
DefaultViewLocator.ResolveView(object)usestypeof(IViewFor<>).MakeGenericType(vmType)to resolve views at runtime. This breaks under AOT compilation and trimming — a problem for RoutedViewHost and ViewModelViewHost across all platforms (WPF, MAUI, WinForms, Apple/iOS) where the view model type is only known at runtime (e.g., navigation stacks withIRoutableViewModel).The source generator now scans for all
IViewFor<T>implementations at compile time and generates a type-switch dispatch function. At runtime,DefaultViewLocator.ResolveView(object)calls this generated dispatch first — no reflection, noMakeGenericType, fully AOT and trimming safe. Views are resolved through a three-tier strategy:IViewFor<T>, then falls back to direct construction if the view has an accessible parameterless constructor.Map<TVM, TView>()registrations via the fluentConfigureViewLocatorbuilder API.The builder integration follows the same pattern as the rest of ReactiveUI.Binding —
ConfigureViewLocatoris available on bothIReactiveUIBindingBuilderand as anIAppBuilderextension viaBuilderMixins, supporting fluent configuration like:Tests