55using System . Diagnostics . CodeAnalysis ;
66using System . Reflection ;
77using Microsoft . AspNetCore . Components . Reflection ;
8+ using Microsoft . AspNetCore . Components . RenderTree ;
89using static Microsoft . AspNetCore . Internal . LinkerFlags ;
910
1011namespace Microsoft . AspNetCore . Components ;
@@ -14,46 +15,70 @@ 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 , ComponentTypeInfoCacheEntry > _cachedComponentTypeInfo = new ( ) ;
1819
1920 private readonly IComponentActivator _componentActivator ;
21+ private readonly Renderer _renderer ;
2022
21- public ComponentFactory ( IComponentActivator componentActivator )
23+ public ComponentFactory ( IComponentActivator componentActivator , Renderer renderer )
2224 {
2325 _componentActivator = componentActivator ?? throw new ArgumentNullException ( nameof ( componentActivator ) ) ;
26+ _renderer = renderer ?? throw new ArgumentNullException ( nameof ( renderer ) ) ;
2427 }
2528
26- public static void ClearCache ( ) => _cachedInitializers . Clear ( ) ;
29+ public static void ClearCache ( ) => _cachedComponentTypeInfo . Clear ( ) ;
2730
28- public IComponent InstantiateComponent ( IServiceProvider serviceProvider , [ DynamicallyAccessedMembers ( Component ) ] Type componentType )
31+ private static ComponentTypeInfoCacheEntry GetComponentTypeInfo ( [ DynamicallyAccessedMembers ( Component ) ] Type componentType )
2932 {
30- var component = _componentActivator . CreateInstance ( componentType ) ;
33+ // Unfortunately we can't use 'GetOrAdd' here because the DynamicallyAccessedMembers annotation doesn't flow through to the
34+ // callback, so it becomes an IL2111 warning. The following is equivalent and thread-safe because it's a ConcurrentDictionary
35+ // and it doesn't matter if we build a cache entry more than once.
36+ if ( ! _cachedComponentTypeInfo . TryGetValue ( componentType , out var cacheEntry ) )
37+ {
38+ var componentTypeRenderMode = componentType . GetCustomAttribute < RenderModeAttribute > ( ) ? . Mode ;
39+ cacheEntry = new ComponentTypeInfoCacheEntry (
40+ componentTypeRenderMode ,
41+ CreatePropertyInjector ( componentType ) ) ;
42+ _cachedComponentTypeInfo . TryAdd ( componentType , cacheEntry ) ;
43+ }
44+
45+ return cacheEntry ;
46+ }
47+
48+ public IComponent InstantiateComponent ( IServiceProvider serviceProvider , [ DynamicallyAccessedMembers ( Component ) ] Type componentType , int ? parentComponentId )
49+ {
50+ var componentTypeInfo = GetComponentTypeInfo ( componentType ) ;
51+ var component = componentTypeInfo . ComponentTypeRenderMode is null
52+ ? _componentActivator . CreateInstance ( componentType )
53+ : _renderer . ResolveComponentForRenderMode ( componentType , parentComponentId , _componentActivator , componentTypeInfo . ComponentTypeRenderMode ) ;
54+
3155 if ( component is null )
3256 {
33- // The default activator will never do this, but an externally-supplied one might
57+ // The default activator/resolver will never do this, but an externally-supplied one might
3458 throw new InvalidOperationException ( $ "The component activator returned a null value for a component of type { componentType . FullName } .") ;
3559 }
3660
37- PerformPropertyInjection ( serviceProvider , component ) ;
61+ if ( component . GetType ( ) == componentType )
62+ {
63+ // Fast, common case: use the cached data we already looked up
64+ componentTypeInfo . PerformPropertyInjection ( serviceProvider , component ) ;
65+ }
66+ else
67+ {
68+ // Uncommon case where the activator/resolver returned a different type. Needs an extra cache lookup.
69+ PerformPropertyInjection ( serviceProvider , component ) ;
70+ }
71+
3872 return component ;
3973 }
4074
4175 private static void PerformPropertyInjection ( IServiceProvider serviceProvider , IComponent instance )
4276 {
43- // This is thread-safe because _cachedInitializers is a ConcurrentDictionary.
44- // We might generate the initializer more than once for a given type, but would
45- // still produce the correct result.
46- var instanceType = instance . GetType ( ) ;
47- if ( ! _cachedInitializers . TryGetValue ( instanceType , out var initializer ) )
48- {
49- initializer = CreateInitializer ( instanceType ) ;
50- _cachedInitializers . TryAdd ( instanceType , initializer ) ;
51- }
52-
53- initializer ( serviceProvider , instance ) ;
77+ var componentTypeInfo = GetComponentTypeInfo ( instance . GetType ( ) ) ;
78+ componentTypeInfo . PerformPropertyInjection ( serviceProvider , instance ) ;
5479 }
5580
56- private static Action < IServiceProvider , IComponent > CreateInitializer ( [ DynamicallyAccessedMembers ( Component ) ] Type type )
81+ private static Action < IServiceProvider , IComponent > CreatePropertyInjector ( [ DynamicallyAccessedMembers ( Component ) ] Type type )
5782 {
5883 // Do all the reflection up front
5984 List < ( string name , Type propertyType , PropertySetter setter ) > ? injectables = null ;
@@ -93,4 +118,9 @@ void Initialize(IServiceProvider serviceProvider, IComponent component)
93118 }
94119 }
95120 }
121+
122+ // Tracks information about a specific component type that ComponentFactory uses
123+ private record class ComponentTypeInfoCacheEntry (
124+ IComponentRenderMode ? ComponentTypeRenderMode ,
125+ Action < IServiceProvider , IComponent > PerformPropertyInjection ) ;
96126}
0 commit comments