Make ComponentFactory do all the reflection up-front and cache the resulting delegates
This commit is contained in:
parent
ceacd489aa
commit
e524994734
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Components
|
||||
|
|
@ -11,6 +13,8 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
|||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly static BindingFlags _injectablePropertyBindingFlags
|
||||
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
private readonly IDictionary<Type, Action<IComponent>> _cachedInitializers
|
||||
= new Dictionary<Type, Action<IComponent>>();
|
||||
|
||||
public ComponentFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
|
|
@ -33,25 +37,68 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
|||
|
||||
private void PerformPropertyInjection(IComponent instance)
|
||||
{
|
||||
// TODO: Cache delegates, etc
|
||||
var type = instance.GetType();
|
||||
var properties = type.GetTypeInfo().GetProperties(_injectablePropertyBindingFlags);
|
||||
foreach (var property in properties)
|
||||
var instanceType = instance.GetType();
|
||||
if (!_cachedInitializers.TryGetValue(instanceType, out var initializer))
|
||||
{
|
||||
var injectAttribute = property.GetCustomAttribute<InjectAttribute>();
|
||||
if (injectAttribute != null)
|
||||
initializer = CreateInitializer(instanceType);
|
||||
_cachedInitializers[instanceType] = initializer;
|
||||
}
|
||||
|
||||
initializer(instance);
|
||||
}
|
||||
|
||||
private Action<IComponent> CreateInitializer(Type type)
|
||||
{
|
||||
// Do all the reflection up front
|
||||
var injectableProperties = type.GetTypeInfo()
|
||||
.GetProperties(_injectablePropertyBindingFlags)
|
||||
.Where(p => p.GetCustomAttribute<InjectAttribute>() != null)
|
||||
.Where(p => p.SetMethod != null);
|
||||
var injectables = injectableProperties.Select(property =>
|
||||
(
|
||||
propertyName: property.Name,
|
||||
propertyType: property.PropertyType,
|
||||
setter: (IPropertySetter)Activator.CreateInstance(
|
||||
typeof(PropertySetter<,>).MakeGenericType(type, property.PropertyType),
|
||||
property.SetMethod)
|
||||
)).ToArray();
|
||||
|
||||
// Return an action whose closure can write all the injected properties
|
||||
// without any further reflection calls (just typecasts)
|
||||
return instance =>
|
||||
{
|
||||
foreach (var injectable in injectables)
|
||||
{
|
||||
var serviceInstance = _serviceProvider.GetService(property.PropertyType);
|
||||
var serviceInstance = _serviceProvider.GetService(injectable.propertyType);
|
||||
if (serviceInstance == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot provide value for property " +
|
||||
$"'{property.Name}' on type '{type.FullName}'. There is no registered " +
|
||||
$"service of type '{property.PropertyType.FullName}'.");
|
||||
$"'{injectable.propertyName}' on type '{type.FullName}'. There is no " +
|
||||
$"registered service of type '{injectable.propertyType}'.");
|
||||
}
|
||||
|
||||
property.SetValue(instance, serviceInstance);
|
||||
injectable.setter.SetValue(instance, serviceInstance);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private interface IPropertySetter
|
||||
{
|
||||
void SetValue(object target, object value);
|
||||
}
|
||||
|
||||
private class PropertySetter<TTarget, TValue> : IPropertySetter
|
||||
{
|
||||
private readonly Action<TTarget, TValue> _setterDelegate;
|
||||
|
||||
public PropertySetter(MethodInfo setMethod)
|
||||
{
|
||||
_setterDelegate = (Action<TTarget, TValue>)Delegate.CreateDelegate(
|
||||
typeof(Action<TTarget, TValue>), setMethod);
|
||||
}
|
||||
|
||||
public void SetValue(object target, object value)
|
||||
=> _setterDelegate((TTarget)target, (TValue)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.Null(HasStaticProperties.StaticPropertyWithoutInject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IgnoresGetOnlyProperties()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = InstantiateComponent<HasGetOnlyProperty>();
|
||||
|
||||
// Assert
|
||||
Assert.Null(component.MyService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfNoSuchServiceIsRegistered()
|
||||
{
|
||||
|
|
@ -119,7 +129,12 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
[Inject] public static IMyService StaticPropertyWithInject { get; set; }
|
||||
public static IMyService StaticPropertyWithoutInject { get; set; }
|
||||
}
|
||||
|
||||
|
||||
class HasGetOnlyProperty : TestComponent
|
||||
{
|
||||
[Inject] public IMyService MyService { get; }
|
||||
}
|
||||
|
||||
class HasInjectableProperty : TestComponent
|
||||
{
|
||||
[Inject] public IMyService MyService { get; set; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue