Simplistic implementation of property injection
This commit is contained in:
parent
ce10e6fa19
commit
ceacd489aa
|
|
@ -2,12 +2,15 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Components
|
||||
{
|
||||
internal class ComponentFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly static BindingFlags _injectablePropertyBindingFlags
|
||||
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public ComponentFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
|
|
@ -30,7 +33,25 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
|||
|
||||
private void PerformPropertyInjection(IComponent instance)
|
||||
{
|
||||
// TODO
|
||||
// TODO: Cache delegates, etc
|
||||
var type = instance.GetType();
|
||||
var properties = type.GetTypeInfo().GetProperties(_injectablePropertyBindingFlags);
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var injectAttribute = property.GetCustomAttribute<InjectAttribute>();
|
||||
if (injectAttribute != null)
|
||||
{
|
||||
var serviceInstance = _serviceProvider.GetService(property.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}'.");
|
||||
}
|
||||
|
||||
property.SetValue(instance, serviceInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the associated property should have a value injected from the
|
||||
/// service provider during initialization.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class InjectAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Test
|
||||
{
|
||||
public class DependencyInjectionTest
|
||||
{
|
||||
private readonly TestRenderer _renderer;
|
||||
private readonly TestServiceProvider _serviceProvider;
|
||||
|
||||
public DependencyInjectionTest()
|
||||
{
|
||||
_serviceProvider = new TestServiceProvider();
|
||||
_renderer = new TestRenderer(_serviceProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IgnoresPropertiesWithoutInjectAttribute()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = InstantiateComponent<HasPropertiesWithoutInjectAttribute>();
|
||||
|
||||
// Assert
|
||||
Assert.Null(component.SomeProperty);
|
||||
Assert.Null(component.PrivatePropertyValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IgnoresStaticProperties()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = InstantiateComponent<HasStaticProperties>();
|
||||
|
||||
// Assert
|
||||
Assert.Null(HasStaticProperties.StaticPropertyWithInject);
|
||||
Assert.Null(HasStaticProperties.StaticPropertyWithoutInject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfNoSuchServiceIsRegistered()
|
||||
{
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
InstantiateComponent<HasInjectableProperty>();
|
||||
});
|
||||
|
||||
Assert.Equal($"Cannot provide value for property '{nameof(HasInjectableProperty.MyService)}' " +
|
||||
$"on type '{typeof(HasInjectableProperty).FullName}'. There is no registered service " +
|
||||
$"of type '{typeof(IMyService).FullName}'.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsInjectablePropertyValueIfServiceIsRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var serviceInstance = new MyServiceImplementation();
|
||||
_serviceProvider.AddService<IMyService>(serviceInstance);
|
||||
|
||||
// Act
|
||||
var instance = InstantiateComponent<HasInjectableProperty>();
|
||||
|
||||
// Assert
|
||||
Assert.Same(serviceInstance, instance.MyService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandlesInjectablePropertyScenarios()
|
||||
{
|
||||
// Arrange
|
||||
var serviceInstance = new MyServiceImplementation();
|
||||
var otherServiceInstance = new MyOtherServiceImplementation();
|
||||
var concreteServiceInstance = new MyConcreteService();
|
||||
_serviceProvider.AddService<IMyService>(serviceInstance);
|
||||
_serviceProvider.AddService<IMyOtherService>(otherServiceInstance);
|
||||
_serviceProvider.AddService(concreteServiceInstance);
|
||||
|
||||
// Act
|
||||
var instance = InstantiateComponent<HasManyInjectableProperties>();
|
||||
|
||||
// Assert
|
||||
Assert.Same(serviceInstance, instance.PublicReadWrite);
|
||||
Assert.Same(serviceInstance, instance.PublicReadOnly);
|
||||
Assert.Same(serviceInstance, instance.PrivateValue);
|
||||
Assert.Same(otherServiceInstance, instance.DifferentServiceType);
|
||||
Assert.Same(concreteServiceInstance, instance.ConcreteServiceType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsInheritedInjectableProperties()
|
||||
{
|
||||
// Arrange
|
||||
var serviceInstance = new MyServiceImplementation();
|
||||
_serviceProvider.AddService<IMyService>(serviceInstance);
|
||||
|
||||
// Act
|
||||
var instance = InstantiateComponent<HasInheritedInjectedProperty>();
|
||||
|
||||
// Assert
|
||||
Assert.Same(serviceInstance, instance.MyService);
|
||||
}
|
||||
|
||||
private T InstantiateComponent<T>() where T: IComponent
|
||||
=> _renderer.InstantiateComponent<T>();
|
||||
|
||||
class HasPropertiesWithoutInjectAttribute : TestComponent
|
||||
{
|
||||
public IMyService SomeProperty { get; set; }
|
||||
public IMyService PrivatePropertyValue => PrivateProperty;
|
||||
private IMyService PrivateProperty { get; set; }
|
||||
}
|
||||
|
||||
class HasStaticProperties : TestComponent
|
||||
{
|
||||
[Inject] public static IMyService StaticPropertyWithInject { get; set; }
|
||||
public static IMyService StaticPropertyWithoutInject { get; set; }
|
||||
}
|
||||
|
||||
class HasInjectableProperty : TestComponent
|
||||
{
|
||||
[Inject] public IMyService MyService { get; set; }
|
||||
}
|
||||
|
||||
class HasManyInjectableProperties : TestComponent
|
||||
{
|
||||
[Inject] public IMyService PublicReadWrite { get; set; }
|
||||
[Inject] public IMyService PublicReadOnly { get; private set; }
|
||||
[Inject] private IMyService Private { get; set; }
|
||||
[Inject] public IMyOtherService DifferentServiceType { get; set; }
|
||||
[Inject] public MyConcreteService ConcreteServiceType { get; set; }
|
||||
|
||||
public IMyService PrivateValue => Private;
|
||||
}
|
||||
|
||||
class HasInheritedInjectedProperty : HasInjectableProperty { }
|
||||
|
||||
interface IMyService { }
|
||||
interface IMyOtherService { }
|
||||
|
||||
class MyServiceImplementation : IMyService { }
|
||||
class MyOtherServiceImplementation : IMyOtherService { }
|
||||
class MyConcreteService { }
|
||||
|
||||
class TestComponent : IComponent
|
||||
{
|
||||
// IMPORTANT: The fact that these throw demonstrates that the injection
|
||||
// happens before any of the lifecycle methods. If you change these to
|
||||
// not throw, then be sure also to add a test to verify that injection
|
||||
// occurs before lifecycle methods.
|
||||
|
||||
public void Init(RenderHandle renderHandle)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void SetParameters(ParameterCollection parameters)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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 Microsoft.AspNetCore.Blazor.Components;
|
||||
|
|
@ -11,7 +12,11 @@ namespace Microsoft.AspNetCore.Blazor.Test.Helpers
|
|||
{
|
||||
public class TestRenderer : Renderer
|
||||
{
|
||||
public TestRenderer(): base(new TestServiceProvider())
|
||||
public TestRenderer(): this(new TestServiceProvider())
|
||||
{
|
||||
}
|
||||
|
||||
public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +29,9 @@ namespace Microsoft.AspNetCore.Blazor.Test.Helpers
|
|||
public new void DispatchEvent(int componentId, int eventHandlerId, UIEventArgs args)
|
||||
=> base.DispatchEvent(componentId, eventHandlerId, args);
|
||||
|
||||
public T InstantiateComponent<T>() where T : IComponent
|
||||
=> (T)InstantiateComponent(typeof(T));
|
||||
|
||||
protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
{
|
||||
var capturedBatch = new CapturedBatch();
|
||||
|
|
|
|||
|
|
@ -2,11 +2,21 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Test.Helpers
|
||||
{
|
||||
public class TestServiceProvider : IServiceProvider
|
||||
{
|
||||
public object GetService(Type serviceType) => throw new NotImplementedException();
|
||||
private readonly Dictionary<Type, Func<object>> _factories
|
||||
= new Dictionary<Type, Func<object>>();
|
||||
|
||||
public object GetService(Type serviceType)
|
||||
=> _factories.TryGetValue(serviceType, out var factory)
|
||||
? factory()
|
||||
: null;
|
||||
|
||||
internal void AddService<T>(T value)
|
||||
=> _factories.Add(typeof(T), () => value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue