Make ComponentFactory throw if you try to use [Inject] with a property that has no setter (otherwise it could be confusing)

This commit is contained in:
Steve Sanderson 2018-02-22 21:35:18 +00:00
parent b4a3c852c5
commit 3f9d358004
2 changed files with 29 additions and 17 deletions

View File

@ -52,16 +52,25 @@ namespace Microsoft.AspNetCore.Blazor.Components
// Do all the reflection up front // Do all the reflection up front
var injectableProperties = type.GetTypeInfo() var injectableProperties = type.GetTypeInfo()
.GetProperties(_injectablePropertyBindingFlags) .GetProperties(_injectablePropertyBindingFlags)
.Where(p => p.GetCustomAttribute<InjectAttribute>() != null) .Where(p => p.GetCustomAttribute<InjectAttribute>() != null);
.Where(p => p.SetMethod != null);
var injectables = injectableProperties.Select(property => var injectables = injectableProperties.Select(property =>
( {
propertyName: property.Name, if (property.SetMethod == null)
propertyType: property.PropertyType, {
setter: (IPropertySetter)Activator.CreateInstance( throw new InvalidOperationException($"Cannot provide a value for property " +
typeof(PropertySetter<,>).MakeGenericType(type, property.PropertyType), $"'{property.Name}' on type '{type.FullName}' because the property " +
property.SetMethod) $"has no setter.");
)).ToArray(); }
return
(
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 // Return an action whose closure can write all the injected properties
// without any further reflection calls (just typecasts) // without any further reflection calls (just typecasts)
@ -72,7 +81,7 @@ namespace Microsoft.AspNetCore.Blazor.Components
var serviceInstance = _serviceProvider.GetService(injectable.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 a value for property " +
$"'{injectable.propertyName}' on type '{type.FullName}'. There is no " + $"'{injectable.propertyName}' on type '{type.FullName}'. There is no " +
$"registered service of type '{injectable.propertyType}'."); $"registered service of type '{injectable.propertyType}'.");
} }

View File

@ -42,13 +42,16 @@ namespace Microsoft.AspNetCore.Blazor.Test
} }
[Fact] [Fact]
public void IgnoresGetOnlyProperties() public void ThrowsForInjectablePropertiesWithoutSetter()
{ {
// Arrange/Act var ex = Assert.Throws<InvalidOperationException>(() =>
var component = InstantiateComponent<HasGetOnlyProperty>(); {
InstantiateComponent<HasGetOnlyPropertyWithInject>();
});
// Assert Assert.Equal($"Cannot provide a value for property '{nameof(HasInjectableProperty.MyService)}' " +
Assert.Null(component.MyService); $"on type '{typeof(HasGetOnlyPropertyWithInject).FullName}' because the property " +
$"has no setter.", ex.Message);
} }
[Fact] [Fact]
@ -59,7 +62,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
InstantiateComponent<HasInjectableProperty>(); InstantiateComponent<HasInjectableProperty>();
}); });
Assert.Equal($"Cannot provide value for property '{nameof(HasInjectableProperty.MyService)}' " + Assert.Equal($"Cannot provide a value for property '{nameof(HasInjectableProperty.MyService)}' " +
$"on type '{typeof(HasInjectableProperty).FullName}'. There is no registered service " + $"on type '{typeof(HasInjectableProperty).FullName}'. There is no registered service " +
$"of type '{typeof(IMyService).FullName}'.", ex.Message); $"of type '{typeof(IMyService).FullName}'.", ex.Message);
} }
@ -130,7 +133,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
public static IMyService StaticPropertyWithoutInject { get; set; } public static IMyService StaticPropertyWithoutInject { get; set; }
} }
class HasGetOnlyProperty : TestComponent class HasGetOnlyPropertyWithInject : TestComponent
{ {
[Inject] public IMyService MyService { get; } [Inject] public IMyService MyService { get; }
} }