Make ComponentFactory do all the reflection up-front and cache the resulting delegates

This commit is contained in:
Steve Sanderson 2018-02-22 18:45:14 +00:00
parent ceacd489aa
commit e524994734
2 changed files with 73 additions and 11 deletions

View File

@ -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);
}
}
}

View File

@ -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; }