Skip to content

Commit 197d527

Browse files
committed
Activate components by default
1 parent 4310f2c commit 197d527

10 files changed

+112
-72
lines changed

src/Components/Components/src/ComponentFactory.cs

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics.CodeAnalysis;
66
using System.Reflection;
77
using Microsoft.AspNetCore.Components.Reflection;
8+
using Microsoft.Extensions.DependencyInjection;
89
using static Microsoft.AspNetCore.Internal.LinkerFlags;
910

1011
namespace Microsoft.AspNetCore.Components;
@@ -14,46 +15,76 @@ internal sealed class ComponentFactory
1415
private const BindingFlags _injectablePropertyBindingFlags
1516
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
1617

17-
private static readonly ConcurrentDictionary<Type, Action<IServiceProvider, IComponent>> _cachedInitializers = new();
18+
private static readonly ConcurrentDictionary<Type, (Action<IServiceProvider, IComponent> propertyInitializers, ObjectFactory? componentInitializer)> _cachedInitializers = new();
19+
private readonly IComponentActivator? _componentActivator;
1820

19-
private readonly IComponentActivator _componentActivator;
20-
21-
public ComponentFactory(IComponentActivator componentActivator)
21+
public ComponentFactory(IComponentActivator? componentActivator)
2222
{
23-
_componentActivator = componentActivator ?? throw new ArgumentNullException(nameof(componentActivator));
23+
_componentActivator = componentActivator;
2424
}
2525

2626
public static void ClearCache() => _cachedInitializers.Clear();
2727

2828
public IComponent InstantiateComponent(IServiceProvider serviceProvider, [DynamicallyAccessedMembers(Component)] Type componentType)
2929
{
30-
var component = _componentActivator.CreateInstance(componentType);
31-
if (component is null)
30+
if (_componentActivator is not null)
3231
{
33-
// The default activator will never do this, but an externally-supplied one might
34-
throw new InvalidOperationException($"The component activator returned a null value for a component of type {componentType.FullName}.");
32+
return InstantiateWithActivator(_componentActivator, serviceProvider, componentType);
3533
}
3634

37-
PerformPropertyInjection(serviceProvider, component);
35+
return InstantiateDefault(serviceProvider, componentType);
36+
}
37+
38+
private static IComponent InstantiateDefault(IServiceProvider serviceProvider, [DynamicallyAccessedMembers(Component)] Type componentType)
39+
{
40+
// This is thread-safe because _cachedInitializers is a ConcurrentDictionary.
41+
// We might generate the initializer more than once for a given type, but would
42+
// still produce the correct result.
43+
if (!_cachedInitializers.TryGetValue(componentType, out var initializer))
44+
{
45+
if (!typeof(IComponent).IsAssignableFrom(componentType))
46+
{
47+
throw new ArgumentException($"The type {componentType.FullName} does not implement {nameof(IComponent)}.", nameof(componentType));
48+
}
49+
50+
initializer = (CreatePropertyInitializer(componentType), ActivatorUtilities.CreateFactory(componentType, Type.EmptyTypes));
51+
_cachedInitializers.TryAdd(componentType, initializer);
52+
}
53+
54+
var (propertyInitializer, componentInitializer) = initializer;
55+
var component = (IComponent)componentInitializer!.Invoke(serviceProvider, Array.Empty<object?>());
56+
propertyInitializer(serviceProvider, component);
3857
return component;
3958
}
4059

41-
private static void PerformPropertyInjection(IServiceProvider serviceProvider, IComponent instance)
60+
private static IComponent InstantiateWithActivator(IComponentActivator componentActivator, IServiceProvider serviceProvider, [DynamicallyAccessedMembers(Component)] Type componentType)
4261
{
62+
var component = componentActivator.CreateInstance(componentType);
63+
if (component is null)
64+
{
65+
// A user implemented IComponentActivator might return null.
66+
throw new InvalidOperationException($"The component activator returned a null value for a component of type {componentType.FullName}.");
67+
}
68+
69+
// Use the activated type instead of specified type since the activator may return different/ derived instances.
70+
componentType = component.GetType();
71+
4372
// This is thread-safe because _cachedInitializers is a ConcurrentDictionary.
4473
// We might generate the initializer more than once for a given type, but would
4574
// still produce the correct result.
46-
var instanceType = instance.GetType();
47-
if (!_cachedInitializers.TryGetValue(instanceType, out var initializer))
75+
if (!_cachedInitializers.TryGetValue(componentType, out var initializer))
4876
{
49-
initializer = CreateInitializer(instanceType);
50-
_cachedInitializers.TryAdd(instanceType, initializer);
77+
initializer = (CreatePropertyInitializer(componentType), componentInitializer: null);
78+
_cachedInitializers.TryAdd(componentType, initializer);
5179
}
5280

53-
initializer(serviceProvider, instance);
81+
var (propertyInitializer, _) = initializer;
82+
83+
propertyInitializer(serviceProvider, component);
84+
return component;
5485
}
5586

56-
private static Action<IServiceProvider, IComponent> CreateInitializer([DynamicallyAccessedMembers(Component)] Type type)
87+
private static Action<IServiceProvider, IComponent> CreatePropertyInitializer([DynamicallyAccessedMembers(Component)] Type type)
5788
{
5889
// Do all the reflection up front
5990
List<(string name, Type propertyType, PropertySetter setter)>? injectables = null;

src/Components/Components/src/DefaultComponentActivator.cs

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/Components/Components/src/Microsoft.AspNetCore.Components.WarningSuppressions.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<linker>
3-
<assembly fullname="Microsoft.AspNetCore.Components, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
3+
<assembly fullname="Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
44
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
55
<argument>ILLink</argument>
66
<argument>IL2026</argument>
@@ -41,7 +41,7 @@
4141
<argument>ILLink</argument>
4242
<argument>IL2072</argument>
4343
<property name="Scope">member</property>
44-
<property name="Target">M:Microsoft.AspNetCore.Components.ComponentFactory.PerformPropertyInjection(System.IServiceProvider,Microsoft.AspNetCore.Components.IComponent)</property>
44+
<property name="Target">M:Microsoft.AspNetCore.Components.ComponentFactory.InstantiateWithActivator(Microsoft.AspNetCore.Components.IComponentActivator,System.IServiceProvider,System.Type)</property>
4545
</attribute>
4646
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
4747
<argument>ILLink</argument>
@@ -59,7 +59,7 @@
5959
<argument>ILLink</argument>
6060
<argument>IL2077</argument>
6161
<property name="Scope">member</property>
62-
<property name="Target">M:Microsoft.AspNetCore.Components.ComponentFactory.CreateInitializer(System.Type)</property>
62+
<property name="Target">M:Microsoft.AspNetCore.Components.ComponentFactory.CreatePropertyInitializer(System.Type)</property>
6363
</attribute>
6464
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
6565
<argument>ILLink</argument>

src/Components/Components/src/RenderTree/Renderer.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public event UnhandledExceptionEventHandler UnhandledSynchronizationException
6464
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to be used when initializing components.</param>
6565
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
6666
public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
67-
: this(serviceProvider, loggerFactory, GetComponentActivatorOrDefault(serviceProvider))
67+
: this(serviceProvider, loggerFactory, GetComponentActivatorOrDefault(serviceProvider)!)
6868
{
6969
// This overload is provided for back-compatibility
7070
}
@@ -87,22 +87,16 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
8787
throw new ArgumentNullException(nameof(loggerFactory));
8888
}
8989

90-
if (componentActivator is null)
91-
{
92-
throw new ArgumentNullException(nameof(componentActivator));
93-
}
94-
9590
_serviceProvider = serviceProvider;
9691
_logger = loggerFactory.CreateLogger<Renderer>();
9792
_componentFactory = new ComponentFactory(componentActivator);
9893
}
9994

10095
internal HotReloadManager HotReloadManager { get; set; } = HotReloadManager.Default;
10196

102-
private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvider serviceProvider)
97+
private static IComponentActivator? GetComponentActivatorOrDefault(IServiceProvider serviceProvider)
10398
{
104-
return serviceProvider.GetService<IComponentActivator>()
105-
?? DefaultComponentActivator.Instance;
99+
return serviceProvider.GetService<IComponentActivator>();
106100
}
107101

108102
/// <summary>

src/Components/Components/test/ComponentFactoryTest.cs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public void InstantiateComponent_CreatesInstance()
1212
{
1313
// Arrange
1414
var componentType = typeof(EmptyComponent);
15-
var factory = new ComponentFactory(new DefaultComponentActivator());
15+
var factory = new ComponentFactory(null);
1616

1717
// Act
1818
var instance = factory.InstantiateComponent(GetServiceProvider(), componentType);
@@ -22,12 +22,30 @@ public void InstantiateComponent_CreatesInstance()
2222
Assert.IsType<EmptyComponent>(instance);
2323
}
2424

25+
[Fact]
26+
public void InstantiateComponent_CreatesInstance_WithTypeActivation()
27+
{
28+
// Arrange
29+
var componentType = typeof(ComponentWithConstructorInjection);
30+
var factory = new ComponentFactory(null);
31+
32+
// Act
33+
var instance = factory.InstantiateComponent(GetServiceProvider(), componentType);
34+
35+
// Assert
36+
Assert.NotNull(instance);
37+
var component = Assert.IsType<ComponentWithConstructorInjection>(instance);
38+
Assert.NotNull(component.Property1);
39+
Assert.NotNull(component.Property2);
40+
Assert.NotNull(component.Property3); // Property injection should still work.
41+
}
42+
2543
[Fact]
2644
public void InstantiateComponent_CreatesInstance_NonComponent()
2745
{
2846
// Arrange
2947
var componentType = typeof(List<string>);
30-
var factory = new ComponentFactory(new DefaultComponentActivator());
48+
var factory = new ComponentFactory(null);
3149

3250
// Assert
3351
var ex = Assert.Throws<ArgumentException>(() => factory.InstantiateComponent(GetServiceProvider(), componentType));
@@ -95,7 +113,7 @@ public void InstantiateComponent_IgnoresPropertiesWithoutInjectAttribute()
95113
{
96114
// Arrange
97115
var componentType = typeof(ComponentWithNonInjectableProperties);
98-
var factory = new ComponentFactory(new DefaultComponentActivator());
116+
var factory = new ComponentFactory(null);
99117

100118
// Act
101119
var instance = factory.InstantiateComponent(GetServiceProvider(), componentType);
@@ -129,6 +147,31 @@ public Task SetParametersAsync(ParameterView parameters)
129147
}
130148
}
131149

150+
public class ComponentWithConstructorInjection : IComponent
151+
{
152+
public ComponentWithConstructorInjection(TestService1 property1, TestService2 property2)
153+
{
154+
Property1 = property1;
155+
Property2 = property2;
156+
}
157+
158+
public TestService1 Property1 { get; }
159+
public TestService2 Property2 { get; }
160+
161+
[Inject]
162+
public TestService2 Property3 { get; set; }
163+
164+
public void Attach(RenderHandle renderHandle)
165+
{
166+
throw new NotImplementedException();
167+
}
168+
169+
public Task SetParametersAsync(ParameterView parameters)
170+
{
171+
throw new NotImplementedException();
172+
}
173+
}
174+
132175
private class ComponentWithInjectProperties : IComponent
133176
{
134177
[Inject]

src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.WarningSuppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<linker>
3-
<assembly fullname="Microsoft.AspNetCore.Components.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
3+
<assembly fullname="Microsoft.AspNetCore.Components.Forms, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
44
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
55
<argument>ILLink</argument>
66
<argument>IL2026</argument>

src/Components/Samples/BlazorServerApp/Pages/FetchData.razor

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<PageTitle>Weather forecast</PageTitle>
44

55
@using BlazorServerApp.Data
6-
@inject WeatherForecastService ForecastService
76

87
<h1>Weather forecast</h1>
98

@@ -41,6 +40,13 @@ else
4140
@code {
4241
WeatherForecast[]? forecasts;
4342

43+
public FetchData(WeatherForecastService forecastService)
44+
{
45+
ForecastService = forecastService;
46+
}
47+
48+
private WeatherForecastService ForecastService { get; }
49+
4450
protected override async Task OnInitializedAsync()
4551
{
4652
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);

src/Components/Web/src/Microsoft.AspNetCore.Components.Web.WarningSuppressions.xml

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<linker>
3-
<assembly fullname="Microsoft.AspNetCore.Components.Web, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
4-
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
5-
<argument>ILLink</argument>
6-
<argument>IL2026</argument>
7-
<property name="Scope">member</property>
8-
<property name="Target">M:Microsoft.AspNetCore.Components.Web.Infrastructure.JSComponentInterop.SetRootComponentParameters(System.Int32,System.Int32,System.Text.Json.JsonElement,System.Text.Json.JsonSerializerOptions)</property>
9-
</attribute>
10-
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
11-
<argument>ILLink</argument>
12-
<argument>IL2026</argument>
13-
<property name="Scope">member</property>
14-
<property name="Target">M:Microsoft.AspNetCore.Components.Web.WebEventData.ParseEventArgsJson(Microsoft.AspNetCore.Components.RenderTree.Renderer,System.Text.Json.JsonSerializerOptions,System.UInt64,System.String,System.Text.Json.JsonElement)</property>
15-
</attribute>
3+
<assembly fullname="Microsoft.AspNetCore.Components.Web, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
164
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
175
<argument>ILLink</argument>
186
<argument>IL2062</argument>

src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.WarningSuppressions.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<linker>
33
<assembly fullname="Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60">
44
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
@@ -14,4 +14,4 @@
1414
<property name="Target">M:Microsoft.Extensions.DependencyInjection.WebAssemblyAuthenticationServiceCollectionExtensions.&lt;&gt;c__1`1.&lt;AddAuthenticationStateProvider&gt;b__1_0(System.IServiceProvider)</property>
1515
</attribute>
1616
</assembly>
17-
</linker>
17+
</linker>

src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.WarningSuppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<linker>
3-
<assembly fullname="Microsoft.JSInterop, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
3+
<assembly fullname="Microsoft.JSInterop, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
44
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
55
<argument>ILLink</argument>
66
<argument>IL2026</argument>

0 commit comments

Comments
 (0)