Skip to content

Allow the interception of property injection with Blazor Components #28957

@dotnetjunkie

Description

@dotnetjunkie

After a previous discussion (#8886), the IComponentActivator abstraction was introduced in .NET 5 (#19642). Unfortunately, implementations of this new abstraction are not expected to apply any injection, as the documentation for IComponentActivator states:

The activator is not responsible for dependency injection, since the framework performs dependency injection to the resulting instances separately.

Instead, the "responsibility for dependency injection", i.e. property injection, is implemented in the ComponentFactory class. The implementation of property injection, however, is hard coded to use the IServiceProvider abstraction. There is no way to intercept this behavior and ComponentFactory is internal with no option to replace it.

This, unfortunately, makes the given IComponentActivator abstraction useless from a DI perspective (as proposed in #8886). While non-conforming containers would like to intercept the composition of object graphs of Blazor components (including the composition of all their dependencies), they can only intercept the creation of the Blazor component itself; not the injection of its dependencies, because ComponentFactory is responsible for this. A non-conforming container, however, does not replace the built-in container, and when ComponentFactory calls IServiceProvider, it requests the dependency from the built-in container, not from the non-conforming container. As such dependency would not be part of the built-in container, ComponentFactory will throw the following exception:

Cannot provide a value for property '{propertyName}' on type '{type.FullName}'. There is no registered service of type '{propertyType}'.

To work around this situation, users of a non-conforming container would have to:

  • Add dependencies of their Blazor components to the built-in (framework) container instead of their (non-conforming) application container. Because this also holds for indirect dependencies, this effectively leads to the situation where the application container is no longer used.
  • Or the user should prevent using the @inject attribute, as this is hard-wired to the built-in IServiceProvider abstraction. This could be achieved by defining properties inside the @code tag, using a custom attribute that marks the property for DI. InjectAttribute can't be used, because ComponentFactory reacts to that.

Neither of the two options above are practical or desirable and we should try to improve integration to make this possible.

I see two possible solutions:

  1. Move property injection out of ComponentFactory into DefaultComponentActivator and make DefaultComponentActivator public. This allows replacing property injection behavior by intercepting IComponentActivator. By making DefaultComponentActivator public, a custom IComponentActivator implementation can forward the call to the default implementation for framework components, which is important because it will be responsible for doing property injection on framework components.
  2. Add a new abstraction that allows intercepting property injection. Such hypothetical IComponentInitializer abstraction could have a void Initialize(IComponent) method that applies property injection where the framework's DefaultComponentInitializer would get injection logic and caching that is currently part of ComponentFactory.

Both options have their pros and cons:

  • Option 1:
    • Pro: It keeps all construction together, which is more convenient for containers as they typically want to construct object graphs in a single call.
    • Pro: It simplifies contextual injection scenarios. For instance injecting an Logger<MyBlazorComponent> into an ILogger property on a type called MyBlazorComponent. This is a scenario that's easy to miss because the built-in container lacks this ability, while it is often an important feature of many mature DI containers. Keeping construction in this single call, however, benefits conforming containers (like Autofac) as well, because it might allow them to apply contextual property injection as well.
    • Con: It changes the current IComponentActivator contract, which is expected to be "not responsible for dependency injection." This is a breaking change.
  • Option 2:
    • Pro: This can be implemented without introducing a breaking change.
    • Con: It forces object composition to be split, which could introduce complexity for implementations.
    • Con: Likely makes implementing contextual property injection much harder for both conforming and non-conforming containers.

Perhaps there are other options to consider. If you think there are, please post your ideas below.

/cc @davidfowl

Metadata

Metadata

Assignees

Labels

affected-fewThis issue impacts only small number of customersarea-blazorIncludes: Blazor, Razor ComponentsenhancementThis issue represents an ask for new feature or an enhancement to an existing onefeature-blazor-component-modelAny feature that affects the component model for Blazor (Parameters, Rendering, Lifecycle, etc)severity-majorThis label is used by an internal tool

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions