55using System . Diagnostics . CodeAnalysis ;
66using System . Reflection ;
77using Microsoft . AspNetCore . Components . Reflection ;
8+ using Microsoft . AspNetCore . Components . Rendering ;
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 RenderModeResolver _renderModeResolver ;
2022
21- public ComponentFactory ( IComponentActivator componentActivator )
23+ public ComponentFactory ( IComponentActivator componentActivator , RenderModeResolver renderModeResolver )
2224 {
2325 _componentActivator = componentActivator ?? throw new ArgumentNullException ( nameof ( componentActivator ) ) ;
26+ _renderModeResolver = renderModeResolver ?? throw new ArgumentNullException ( nameof ( renderModeResolver ) ) ;
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+ cacheEntry = new ComponentTypeInfoCacheEntry ( GetRenderModeFromComponentType ( componentType ) , CreatePropertyInjector ( componentType ) ) ;
39+ _cachedComponentTypeInfo . TryAdd ( componentType , cacheEntry ) ;
40+ }
41+
42+ return cacheEntry ;
43+ }
44+
45+ public IComponent InstantiateComponent ( IServiceProvider serviceProvider , [ DynamicallyAccessedMembers ( Component ) ] Type componentType , IComponentRenderMode ? callerSpecifiedRenderMode )
46+ {
47+ var componentTypeInfo = GetComponentTypeInfo ( componentType ) ;
48+ var component = callerSpecifiedRenderMode is null && componentTypeInfo . RenderMode is null
49+ ? _componentActivator . CreateInstance ( componentType )
50+ : _renderModeResolver . ResolveComponent ( componentType , _componentActivator , componentTypeInfo . RenderMode , callerSpecifiedRenderMode ) ;
51+
3152 if ( component is null )
3253 {
3354 // The default activator will never do this, but an externally-supplied one might
3455 throw new InvalidOperationException ( $ "The component activator returned a null value for a component of type { componentType . FullName } .") ;
3556 }
3657
37- PerformPropertyInjection ( serviceProvider , component ) ;
58+ if ( component . GetType ( ) == componentType )
59+ {
60+ // Fast, common case: use the cached data we already looked up
61+ componentTypeInfo . PerformPropertyInjection ( serviceProvider , component ) ;
62+ }
63+ else
64+ {
65+ // Uncommon case where the activator/resolver returned a different type. Needs an extra cache lookup.
66+ PerformPropertyInjection ( serviceProvider , component ) ;
67+ }
68+
3869 return component ;
3970 }
4071
4172 private static void PerformPropertyInjection ( IServiceProvider serviceProvider , IComponent instance )
4273 {
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 ) ;
74+ var componentTypeInfo = GetComponentTypeInfo ( instance . GetType ( ) ) ;
75+ componentTypeInfo . PerformPropertyInjection ( serviceProvider , instance ) ;
5476 }
5577
56- private static Action < IServiceProvider , IComponent > CreateInitializer ( [ DynamicallyAccessedMembers ( Component ) ] Type type )
78+ private static IComponentRenderMode ? GetRenderModeFromComponentType ( Type componentType )
79+ => componentType . GetCustomAttribute < RenderModeAttribute > ( ) ? . Mode ;
80+
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 ? RenderMode ,
125+ Action < IServiceProvider , IComponent > PerformPropertyInjection ) ;
96126}
0 commit comments