[Blazor] Introduce IComponentPropertyActivator for property injection#64595
[Blazor] Introduce IComponentPropertyActivator for property injection#64595
Conversation
This PR introduces a new public abstraction IComponentPropertyActivator for Blazor property injection, addressing #63451. - Add IComponentPropertyActivator interface for customizing property injection - Add DefaultComponentPropertyActivator internal implementation - Refactor ComponentFactory to use the new abstraction - Update Renderer to resolve IComponentPropertyActivator from DI - Add comprehensive unit tests for the new functionality
There was a problem hiding this comment.
Pull request overview
This PR introduces a new public extensibility point IComponentPropertyActivator that allows customization of property injection in Blazor components, addressing the gap identified in issue #63451. The change follows the same design pattern as IComponentActivator and enables scenarios like custom DI containers and advanced property injection requirements in Blazor Hybrid applications.
Key Changes:
- New public interface
IComponentPropertyActivatorwith proper XML documentation and trimming annotations - Internal
DefaultComponentPropertyActivatorimplementation extracted fromComponentFactorywith caching and Hot Reload support - Refactored
ComponentFactoryto delegate property injection to the activator, simplifying the class and improving separation of concerns
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/Components/Components/src/IComponentPropertyActivator.cs |
New public interface defining the property activation contract with comprehensive documentation |
src/Components/Components/src/DefaultComponentPropertyActivator.cs |
Default implementation with caching, Hot Reload integration, and keyed service support |
src/Components/Components/src/ComponentFactory.cs |
Refactored to use IComponentPropertyActivator, removing embedded property injection logic and simplifying the class |
src/Components/Components/src/RenderTree/Renderer.cs |
Added DI resolution for IComponentPropertyActivator with fallback to default, integrated cache clearing for Hot Reload |
src/Components/Components/src/PublicAPI.Unshipped.txt |
Added new public API surface entries |
src/Components/Components/test/ComponentFactoryTest.cs |
Updated existing tests with new constructor parameter, added comprehensive tests for custom property activator scenarios |
src/Components/Components/test/RouteViewTest.cs |
Updated test to pass new required IComponentPropertyActivator parameter |
src/Components/Authorization/test/AuthorizeRouteViewTest.cs |
Updated test to pass new required IComponentPropertyActivator parameter |
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
ilonatommy
left a comment
There was a problem hiding this comment.
Looks good to me but I would prefer more reviewers to see it.
Fine with me. Just to set the context. This follows the same pattern as everything else on the framework (MVC/RazorPages/Controllers). The PR is mostly defining the equivalent abstraction for components and moving the code that was on the DefaultComponentFactory into the activator. |
| if (component.GetType() == componentType) | ||
| { | ||
| // Fast, common case: use the cached data we already looked up | ||
| propertyInjector(serviceProvider, component); | ||
| var propertyActivator = _propertyActivator.GetActivator(componentType); | ||
| propertyActivator(serviceProvider, component); | ||
| } | ||
| else | ||
| { |
There was a problem hiding this comment.
Is the if-else is relevant? The code looks identical
There was a problem hiding this comment.
Yeah, I think this can be simplified a bit.
@copilot can you clean this up? We should have tests that validate the behavior is correct whenever the activator returns a subtype and that all the properties for the subtype get correctly injected. There shouldn't be any difference whether the type is the same or a subtype.
| /// <param name="serviceProvider">The <see cref="IServiceProvider"/> to be used when initializing components.</param> | ||
| /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> | ||
| /// <param name="componentActivator">The <see cref="IComponentActivator"/>.</param> | ||
| public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IComponentActivator componentActivator) |
There was a problem hiding this comment.
Why are we taking IComponentActivator as argument, but resolving IComponentPropertyActivator from DI explicitly?
There was a problem hiding this comment.
0ec15ea#diff-329ab76a7fca1f1325c9c8a21d79c9d7731b5a2163b81b7797a86291b53e0153R61-R73
I don't think there was a good reason to actually take in the activator explicitly since we were already taking in the service provider. The change is from a million years ago. I prefer we avoid adding an additional overload for this as there's no good reason for it (we already receive a service provider).
The renderer is essentially instantiated by the hosts (which is us) so I don't think making the dependency explicit (likely the reason at the time) is important. (Especially since there's a default fallback)
…on tests (#64750) * Initial plan * Simplify ComponentFactory and add comprehensive subtype property injection tests Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Summary
This PR introduces a new public abstraction
IComponentPropertyActivatorfor customizing property injection in Blazor components, addressing the extensibility gap identified in #63451.Background
Currently, Blazor's property injection logic (for
[Inject]attributes) is embedded within the internalComponentFactoryclass with no way to customize it. WhileIComponentActivatorexists for customizing component instantiation, there's no equivalent for property injection.This becomes problematic for scenarios like:
Changes
New Public API
Implementation Details
IComponentPropertyActivator: New public interface following the same pattern as MVC's property activatorsDefaultComponentPropertyActivator: Internal default implementation that:ConcurrentDictionary[Inject(Key = "...")]ComponentFactory: Refactored to acceptIComponentPropertyActivatoras a dependencyRenderer: Updated to resolveIComponentPropertyActivatorfrom DI with fallback to defaultTests
Added comprehensive unit tests covering:
API Usage
Related Issues