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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Components
|
namespace Microsoft.AspNetCore.Blazor.Components
|
||||||
|
|
@ -11,6 +13,8 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly static BindingFlags _injectablePropertyBindingFlags
|
private readonly static BindingFlags _injectablePropertyBindingFlags
|
||||||
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||||
|
private readonly IDictionary<Type, Action<IComponent>> _cachedInitializers
|
||||||
|
= new Dictionary<Type, Action<IComponent>>();
|
||||||
|
|
||||||
public ComponentFactory(IServiceProvider serviceProvider)
|
public ComponentFactory(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
|
|
@ -33,25 +37,68 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
||||||
|
|
||||||
private void PerformPropertyInjection(IComponent instance)
|
private void PerformPropertyInjection(IComponent instance)
|
||||||
{
|
{
|
||||||
// TODO: Cache delegates, etc
|
var instanceType = instance.GetType();
|
||||||
var type = instance.GetType();
|
if (!_cachedInitializers.TryGetValue(instanceType, out var initializer))
|
||||||
var properties = type.GetTypeInfo().GetProperties(_injectablePropertyBindingFlags);
|
|
||||||
foreach (var property in properties)
|
|
||||||
{
|
{
|
||||||
var injectAttribute = property.GetCustomAttribute<InjectAttribute>();
|
initializer = CreateInitializer(instanceType);
|
||||||
if (injectAttribute != null)
|
_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)
|
if (serviceInstance == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Cannot provide value for property " +
|
throw new InvalidOperationException($"Cannot provide value for property " +
|
||||||
$"'{property.Name}' on type '{type.FullName}'. There is no registered " +
|
$"'{injectable.propertyName}' on type '{type.FullName}'. There is no " +
|
||||||
$"service of type '{property.PropertyType.FullName}'.");
|
$"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);
|
Assert.Null(HasStaticProperties.StaticPropertyWithoutInject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IgnoresGetOnlyProperties()
|
||||||
|
{
|
||||||
|
// Arrange/Act
|
||||||
|
var component = InstantiateComponent<HasGetOnlyProperty>();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(component.MyService);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ThrowsIfNoSuchServiceIsRegistered()
|
public void ThrowsIfNoSuchServiceIsRegistered()
|
||||||
{
|
{
|
||||||
|
|
@ -119,7 +129,12 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
[Inject] public static IMyService StaticPropertyWithInject { get; set; }
|
[Inject] public static IMyService StaticPropertyWithInject { get; set; }
|
||||||
public static IMyService StaticPropertyWithoutInject { get; set; }
|
public static IMyService StaticPropertyWithoutInject { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HasGetOnlyProperty : TestComponent
|
||||||
|
{
|
||||||
|
[Inject] public IMyService MyService { get; }
|
||||||
|
}
|
||||||
|
|
||||||
class HasInjectableProperty : TestComponent
|
class HasInjectableProperty : TestComponent
|
||||||
{
|
{
|
||||||
[Inject] public IMyService MyService { get; set; }
|
[Inject] public IMyService MyService { get; set; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue