Skip to content

Commit 7e1f4a4

Browse files
committed
Construction injection
1 parent 0027b32 commit 7e1f4a4

File tree

6 files changed

+99
-23
lines changed

6 files changed

+99
-23
lines changed

src/Components/Authorization/test/AuthorizeRouteViewTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public AuthorizeRouteViewTest()
3535

3636
var services = serviceCollection.BuildServiceProvider();
3737
_renderer = new TestRenderer(services);
38-
var componentFactory = new ComponentFactory(new DefaultComponentActivator(), _renderer);
38+
var componentFactory = new ComponentFactory(new DefaultComponentActivator(services), _renderer);
3939
_authorizeRouteViewComponent = (AuthorizeRouteView)componentFactory.InstantiateComponent(services, typeof(AuthorizeRouteView), null, null);
4040
_authorizeRouteViewComponentId = _renderer.AssignRootComponentId(_authorizeRouteViewComponent);
4141
}

src/Components/Components/src/ComponentFactory.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ public IComponent InstantiateComponent(IServiceProvider serviceProvider, [Dynami
9090
private static void PerformPropertyInjection(IServiceProvider serviceProvider, IComponent instance)
9191
{
9292
// Suppressed with "pragma warning disable" so ILLink Roslyn Anayzer doesn't report the warning.
93-
#pragma warning disable IL2072 // 'componentType' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'Microsoft.AspNetCore.Components.ComponentFactory.GetComponentTypeInfo(Type)'.
93+
#pragma warning disable IL2072 // 'componentType' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'Microsoft.AspNetCore.Components.ComponentFactory.GetComponentTypeInfo(Type)'.
9494
var componentTypeInfo = GetComponentTypeInfo(instance.GetType());
95-
#pragma warning restore IL2072 // 'componentType' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'Microsoft.AspNetCore.Components.ComponentFactory.GetComponentTypeInfo(Type)'.
95+
#pragma warning restore IL2072 // 'componentType' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'Microsoft.AspNetCore.Components.ComponentFactory.GetComponentTypeInfo(Type)'.
9696

9797
componentTypeInfo.PerformPropertyInjection(serviceProvider, instance);
9898
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Concurrent;
45
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.Extensions.DependencyInjection;
57

68
namespace Microsoft.AspNetCore.Components;
79

8-
internal sealed class DefaultComponentActivator : IComponentActivator
10+
internal sealed class DefaultComponentActivator(IServiceProvider serviceProvider) : IComponentActivator
911
{
10-
public static IComponentActivator Instance { get; } = new DefaultComponentActivator();
12+
private static readonly ConcurrentDictionary<Type, ObjectFactory> _cachedComponentTypeInfo = new();
13+
14+
public static void ClearCache() => _cachedComponentTypeInfo.Clear();
1115

1216
/// <inheritdoc />
1317
public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type componentType)
@@ -17,6 +21,22 @@ public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessed
1721
throw new ArgumentException($"The type {componentType.FullName} does not implement {nameof(IComponent)}.", nameof(componentType));
1822
}
1923

20-
return (IComponent)Activator.CreateInstance(componentType)!;
24+
var factory = GetObjectFactory(componentType);
25+
26+
return (IComponent)factory(serviceProvider, []);
27+
}
28+
29+
private static ObjectFactory GetObjectFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type componentType)
30+
{
31+
// Unfortunately we can't use 'GetOrAdd' here because the DynamicallyAccessedMembers annotation doesn't flow through to the
32+
// callback, so it becomes an IL2111 warning. The following is equivalent and thread-safe because it's a ConcurrentDictionary
33+
// and it doesn't matter if we build a cache entry more than once.
34+
if (!_cachedComponentTypeInfo.TryGetValue(componentType, out var factory))
35+
{
36+
factory = ActivatorUtilities.CreateFactory(componentType, Type.EmptyTypes);
37+
_cachedComponentTypeInfo.TryAdd(componentType, factory);
38+
}
39+
40+
return factory;
2141
}
2242
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
102102
private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvider serviceProvider)
103103
{
104104
return serviceProvider.GetService<IComponentActivator>()
105-
?? DefaultComponentActivator.Instance;
105+
?? new DefaultComponentActivator(serviceProvider);
106106
}
107107

108108
/// <summary>
@@ -155,6 +155,7 @@ private async void RenderRootComponentsOnHotReload()
155155
// Before re-rendering the root component, also clear any well-known caches in the framework
156156
ComponentFactory.ClearCache();
157157
ComponentProperties.ClearCache();
158+
DefaultComponentActivator.ClearCache();
158159

159160
await Dispatcher.InvokeAsync(() =>
160161
{

src/Components/Components/test/ComponentFactoryTest.cs

+70-15
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ public void InstantiateComponent_CreatesInstance()
1515
{
1616
// Arrange
1717
var componentType = typeof(EmptyComponent);
18-
var factory = new ComponentFactory(new DefaultComponentActivator(), new TestRenderer());
19-
18+
var serviceProvider = GetServiceProvider();
19+
var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), new TestRenderer());
20+
2021
// Act
2122
var instance = factory.InstantiateComponent(GetServiceProvider(), componentType, null, null);
2223

@@ -30,8 +31,9 @@ public void InstantiateComponent_CreatesInstance_NonComponent()
3031
{
3132
// Arrange
3233
var componentType = typeof(List<string>);
33-
var factory = new ComponentFactory(new DefaultComponentActivator(), new TestRenderer());
34-
34+
var serviceProvider = GetServiceProvider();
35+
var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), new TestRenderer());
36+
3537
// Assert
3638
var ex = Assert.Throws<ArgumentException>(() => factory.InstantiateComponent(GetServiceProvider(), componentType, null, null));
3739
Assert.StartsWith($"The type {componentType.FullName} does not implement {nameof(IComponent)}.", ex.Message);
@@ -99,10 +101,11 @@ public void InstantiateComponent_IgnoresPropertiesWithoutInjectAttribute()
99101
{
100102
// Arrange
101103
var componentType = typeof(ComponentWithNonInjectableProperties);
102-
var factory = new ComponentFactory(new DefaultComponentActivator(), new TestRenderer());
104+
var serviceProvider = GetServiceProvider();
105+
var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), new TestRenderer());
103106

104107
// Act
105-
var instance = factory.InstantiateComponent(GetServiceProvider(), componentType, null, null);
108+
var instance = factory.InstantiateComponent(serviceProvider, componentType, null, null);
106109

107110
// Assert
108111
Assert.NotNull(instance);
@@ -119,10 +122,11 @@ public void InstantiateComponent_WithNoRenderMode_DoesNotUseRenderModeResolver()
119122
var componentType = typeof(ComponentWithInjectProperties);
120123
var renderer = new RendererWithResolveComponentForRenderMode(
121124
/* won't be used */ new ComponentWithRenderMode());
122-
var factory = new ComponentFactory(new DefaultComponentActivator(), renderer);
125+
var serviceProvider = GetServiceProvider();
126+
var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), renderer);
123127

124128
// Act
125-
var instance = factory.InstantiateComponent(GetServiceProvider(), componentType, null, null);
129+
var instance = factory.InstantiateComponent(serviceProvider, componentType, null, null);
126130

127131
// Assert
128132
Assert.IsType<ComponentWithInjectProperties>(instance);
@@ -136,11 +140,12 @@ public void InstantiateComponent_WithRenderModeOnComponent_UsesRenderModeResolve
136140
var resolvedComponent = new ComponentWithInjectProperties();
137141
var componentType = typeof(ComponentWithRenderMode);
138142
var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
139-
var componentActivator = new DefaultComponentActivator();
143+
var serviceProvider = GetServiceProvider();
144+
var componentActivator = new DefaultComponentActivator(serviceProvider);
140145
var factory = new ComponentFactory(componentActivator, renderer);
141146

142147
// Act
143-
var instance = (ComponentWithInjectProperties)factory.InstantiateComponent(GetServiceProvider(), componentType, null, 1234);
148+
var instance = (ComponentWithInjectProperties)factory.InstantiateComponent(serviceProvider, componentType, null, 1234);
144149

145150
// Assert
146151
Assert.True(renderer.ResolverWasCalled);
@@ -167,12 +172,13 @@ public void InstantiateComponent_WithDerivedRenderModeOnDerivedComponent_CausesA
167172
var resolvedComponent = new ComponentWithInjectProperties();
168173
var componentType = typeof(DerivedComponentWithRenderMode);
169174
var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
170-
var componentActivator = new DefaultComponentActivator();
175+
var serviceProvider = GetServiceProvider();
176+
var componentActivator = new DefaultComponentActivator(serviceProvider);
171177
var factory = new ComponentFactory(componentActivator, renderer);
172178

173179
// Act/Assert
174180
Assert.Throws<AmbiguousMatchException>(
175-
() => factory.InstantiateComponent(GetServiceProvider(), componentType, null, 1234));
181+
() => factory.InstantiateComponent(serviceProvider, componentType, null, 1234));
176182
}
177183

178184
[Fact]
@@ -185,11 +191,12 @@ public void InstantiateComponent_WithRenderModeOnCallSite_UsesRenderModeResolver
185191
var componentType = typeof(ComponentWithNonInjectableProperties);
186192
var callSiteRenderMode = new TestRenderMode();
187193
var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
188-
var componentActivator = new DefaultComponentActivator();
194+
var serviceProvider = GetServiceProvider();
195+
var componentActivator = new DefaultComponentActivator(serviceProvider);
189196
var factory = new ComponentFactory(componentActivator, renderer);
190197

191198
// Act
192-
var instance = (ComponentWithInjectProperties)factory.InstantiateComponent(GetServiceProvider(), componentType, callSiteRenderMode, 1234);
199+
var instance = (ComponentWithInjectProperties)factory.InstantiateComponent(serviceProvider, componentType, callSiteRenderMode, 1234);
193200

194201
// Assert
195202
Assert.Same(resolvedComponent, instance);
@@ -207,7 +214,8 @@ public void InstantiateComponent_WithRenderModeOnComponentAndCallSite_Throws()
207214
var resolvedComponent = new ComponentWithInjectProperties();
208215
var componentType = typeof(ComponentWithRenderMode);
209216
var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
210-
var componentActivator = new DefaultComponentActivator();
217+
var serviceProvider = GetServiceProvider();
218+
var componentActivator = new DefaultComponentActivator(serviceProvider);
211219
var factory = new ComponentFactory(componentActivator, renderer);
212220

213221
// Even though the two rendermodes are literally the same object, we don't allow specifying any nonnull
@@ -220,6 +228,28 @@ public void InstantiateComponent_WithRenderModeOnComponentAndCallSite_Throws()
220228
Assert.Equal($"The component type '{componentType}' has a fixed rendermode of '{typeof(TestRenderMode)}', so it is not valid to specify any rendermode when using this component.", ex.Message);
221229
}
222230

231+
[Fact]
232+
public void InstantiateComponent_CreatesInstance_WithTypeActivation()
233+
{
234+
// Arrange
235+
var serviceProvider = GetServiceProvider();
236+
var componentType = typeof(ComponentWithConstructorInjection);
237+
var resolvedComponent = new ComponentWithInjectProperties();
238+
var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
239+
var defaultComponentActivator = new DefaultComponentActivator(serviceProvider);
240+
var factory = new ComponentFactory(defaultComponentActivator, renderer);
241+
242+
// Act
243+
var instance = factory.InstantiateComponent(serviceProvider, componentType, null, null);
244+
245+
// Assert
246+
Assert.NotNull(instance);
247+
var component = Assert.IsType<ComponentWithConstructorInjection>(instance);
248+
Assert.NotNull(component.Property1);
249+
Assert.NotNull(component.Property2);
250+
Assert.NotNull(component.Property3); // Property injection should still work.
251+
}
252+
223253
private const string KeyedServiceKey = "my-keyed-service";
224254

225255
private static IServiceProvider GetServiceProvider()
@@ -292,6 +322,31 @@ public Task SetParametersAsync(ParameterView parameters)
292322
}
293323
}
294324

325+
public class ComponentWithConstructorInjection : IComponent
326+
{
327+
public ComponentWithConstructorInjection(TestService1 property1, TestService2 property2)
328+
{
329+
Property1 = property1;
330+
Property2 = property2;
331+
}
332+
333+
public TestService1 Property1 { get; }
334+
public TestService2 Property2 { get; }
335+
336+
[Inject]
337+
public TestService2 Property3 { get; set; }
338+
339+
public void Attach(RenderHandle renderHandle)
340+
{
341+
throw new NotImplementedException();
342+
}
343+
344+
public Task SetParametersAsync(ParameterView parameters)
345+
{
346+
throw new NotImplementedException();
347+
}
348+
}
349+
295350
private class DerivedComponent : ComponentWithInjectProperties
296351
{
297352
public new TestService2 Property4 { get; set; }

src/Components/Components/test/RouteViewTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public RouteViewTest()
2323
var services = serviceCollection.BuildServiceProvider();
2424
_renderer = new TestRenderer(services);
2525

26-
var componentFactory = new ComponentFactory(new DefaultComponentActivator(), _renderer);
26+
var componentFactory = new ComponentFactory(new DefaultComponentActivator(services), _renderer);
2727
_routeViewComponent = (RouteView)componentFactory.InstantiateComponent(services, typeof(RouteView), null, null);
2828

2929
_routeViewComponentId = _renderer.AssignRootComponentId(_routeViewComponent);

0 commit comments

Comments
 (0)