Skip to content

Commit a60072e

Browse files
committed
Add attribute to register intances
1 parent 9d3e3d1 commit a60072e

6 files changed

Lines changed: 341 additions & 6 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
3+
using JetBrains.Annotations;
4+
5+
6+
namespace UnityContainerAttributeRegistration.Attribute
7+
{
8+
/// <summary>
9+
/// Mark a property to be registered to an <see cref="Unity.IUnityContainer" />
10+
/// </summary>
11+
[AttributeUsage(AttributeTargets.Property)]
12+
public class RegisterInstanceAttribute: System.Attribute
13+
{
14+
/// <summary>
15+
/// Candidate for registration to <see cref="Unity" />.
16+
/// </summary>
17+
/// <param name="from"><see cref="Type" /> that will be requested.</param>
18+
/// <param name="lifetimeManager">The <see cref="Unity.Lifetime.IInstanceLifetimeManager" /> that controls the lifetime of the returned instance.</param>
19+
public RegisterInstanceAttribute([CanBeNull] Type from = null,
20+
[CanBeNull] Type lifetimeManager = null)
21+
{
22+
From = from;
23+
LifetimeManager = lifetimeManager;
24+
}
25+
26+
/// <summary>
27+
/// <see cref="Type" /> that will be requested.
28+
/// </summary>
29+
[CanBeNull]
30+
internal Type From { get; }
31+
32+
/// <summary>
33+
/// The <see cref="LifetimeManager" /> that controls the lifetime
34+
/// of the returned instance.
35+
/// </summary>
36+
[CanBeNull]
37+
internal Type LifetimeManager { get; }
38+
}
39+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace UnityContainerAttributeRegistration.Attribute
4+
{
5+
/// <summary>
6+
/// Mark a type to be a provider for <see cref="RegisterInstanceAttribute"/>
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Class)]
9+
public class RegisterInstanceProviderAttribute : System.Attribute
10+
{
11+
}
12+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
6+
using JetBrains.Annotations;
7+
8+
using Unity;
9+
using Unity.Lifetime;
10+
11+
using UnityContainerAttributeRegistration.Provider;
12+
using UnityContainerAttributeRegistration.Attribute;
13+
using UnityContainerAttributeRegistration.Exention;
14+
15+
16+
namespace UnityContainerAttributeRegistration.Populator
17+
{
18+
internal class InstancePopulator : Populator
19+
{
20+
/// <summary>
21+
/// ctor
22+
/// </summary>
23+
/// <param name="appDomain">Used <see cref="IAssemblyProvider" /> for searching for candidates.</param>
24+
public InstancePopulator(IAssemblyProvider appDomain) : base(appDomain)
25+
{
26+
}
27+
28+
/// <summary>
29+
/// Populate the passed <paramref name="container" />.
30+
/// </summary>
31+
/// <param name="container"><see cref="IUnityContainer" /> to populate.</param>
32+
/// <returns>Passed <paramref name="container" />.</returns>
33+
/// <exception cref="InvalidOperationException">Class type must not be static or abstract.</exception>
34+
public override IUnityContainer Populate(IUnityContainer container)
35+
{
36+
IList<Type> typesWithAttribute = GetTypesWith<RegisterInstanceProviderAttribute>(TypeDefined.Inherit)
37+
.ToList();
38+
39+
IEnumerable<InstanceToRegister> instancesToRegister =
40+
typesWithAttribute.SelectMany(providerClassType => GetInstancesToRegisterFor(container, providerClassType));
41+
42+
foreach(InstanceToRegister instanceToRegister in instancesToRegister)
43+
{
44+
Type type = instanceToRegister.Type;
45+
object instance = instanceToRegister.Instance;
46+
IInstanceLifetimeManager lifetimeManager = instanceToRegister.LifetimeManager;
47+
container.RegisterInstance(type, instance, lifetimeManager);
48+
}
49+
50+
return container;
51+
}
52+
53+
/// <summary>
54+
/// Create a list of <see cref="InstanceToRegister"/> depending on class marked with <see cref="RegisterInstanceProviderAttribute"/>
55+
/// </summary>
56+
/// <param name="container"><see cref="IUnityContainer"/> to resolve <paramref name="providerClassType"/></param>
57+
/// <param name="providerClassType">Class type used to search for <see cref="RegisterInstanceAttribute"/></param>
58+
/// <returns>List of instances to register with all needed parameters</returns>
59+
/// <exception cref="InvalidOperationException"><paramref name="providerClassType" /> type must not be static or abstract.</exception>
60+
private IEnumerable<InstanceToRegister> GetInstancesToRegisterFor(IUnityContainer container, Type providerClassType)
61+
{
62+
if(providerClassType.IsStatic() || providerClassType.IsAbstract)
63+
{
64+
throw new InvalidOperationException(
65+
$"Class type must not be static or abstract to be used with RegisterTypeAttribute: {providerClassType.FullName}");
66+
}
67+
68+
object providerClassInstance = container.Resolve(providerClassType);
69+
PropertyInfo[] properties = providerClassType.GetProperties();
70+
71+
return properties.Where(info => info.CustomAttributes.Any(data => data.AttributeType == typeof(RegisterInstanceAttribute)))
72+
.Select(info =>
73+
{
74+
object instance = info.GetValue(providerClassInstance);
75+
RegisterInstanceAttribute attribute = info.GetCustomAttribute<RegisterInstanceAttribute>();
76+
Type from = attribute.From;
77+
IInstanceLifetimeManager lifetimeManager = attribute.LifetimeManager == null ? null : GetInstanceByType<IInstanceLifetimeManager>(attribute.LifetimeManager);
78+
79+
return new InstanceToRegister(instance, from, lifetimeManager);
80+
})
81+
.ToList();
82+
}
83+
84+
/// <summary>
85+
/// Wrapper to regsiter isntances
86+
/// </summary>
87+
private sealed class InstanceToRegister
88+
{
89+
public InstanceToRegister([CanBeNull] object instance,
90+
[CanBeNull] Type type,
91+
[CanBeNull] IInstanceLifetimeManager lifetimeManager)
92+
{
93+
Instance = instance;
94+
Type = type;
95+
LifetimeManager = lifetimeManager;
96+
}
97+
98+
/// <summary>
99+
/// Concrete instance to register
100+
/// </summary>
101+
[CanBeNull]
102+
public object Instance { get; }
103+
104+
/// <summary>
105+
/// Requested type
106+
/// </summary>
107+
[CanBeNull]
108+
public Type Type { get; }
109+
110+
/// <summary>
111+
/// Used lifetime manager
112+
/// </summary>
113+
[CanBeNull]
114+
public IInstanceLifetimeManager LifetimeManager { get; }
115+
}
116+
}
117+
}

src/UnityContainerAttributeRegistration/UnityContainerPopulator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ namespace UnityContainerAttributeRegistration
1313
/// </summary>
1414
public sealed class UnityContainerPopulator
1515
{
16-
private readonly IAssemblyProvider appDomain;
17-
private readonly IPopulator TypePopulator;
16+
private readonly IPopulator typePopulator;
17+
private readonly IPopulator instancePopulator;
1818

1919
/// <summary>
2020
/// Use <see cref="System.AppDomain.CurrentDomain" /> to populate an <see cref="Unity.IUnityContainer" />
@@ -29,9 +29,8 @@ public UnityContainerPopulator() : this(new AssemblyProvider())
2929
/// <param name="appDomain">Custom <see cref="IAssemblyProvider" /></param>
3030
public UnityContainerPopulator([NotNull] IAssemblyProvider appDomain)
3131
{
32-
this.appDomain = appDomain;
33-
34-
TypePopulator = new TypePopulator(appDomain);
32+
typePopulator = new TypePopulator(appDomain);
33+
instancePopulator = new InstancePopulator(appDomain);
3534
}
3635

3736
/// <summary>
@@ -52,7 +51,8 @@ public IUnityContainer Populate()
5251
/// </returns>
5352
public IUnityContainer Populate([NotNull] IUnityContainer container)
5453
{
55-
TypePopulator.Populate(container);
54+
typePopulator.Populate(container);
55+
instancePopulator.Populate(container);
5656

5757
return container;
5858
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using Unity.Lifetime;
2+
3+
using UnityContainerAttributeRegistration.Attribute;
4+
5+
6+
namespace UnityContainerAttributeRegistrationTest.Assets.RegistertInstanceTestClasses
7+
{
8+
[RegisterInstanceProvider]
9+
public class DefaultProvider
10+
{
11+
[RegisterInstance]
12+
public AnyClass Value
13+
{
14+
get => new AnyClass();
15+
}
16+
}
17+
18+
[RegisterInstanceProvider]
19+
public class ProviderUsingFromWithoutLifetimeManager
20+
{
21+
[RegisterInstance(typeof(IAnyInterface))]
22+
public AnyClass Value
23+
{
24+
get => new AnyClass();
25+
}
26+
}
27+
28+
[RegisterInstanceProvider]
29+
public class ProviderUsingFromWithSingletonLifetimeManager
30+
{
31+
[RegisterInstance(typeof(IAnyInterface), typeof(SingletonLifetimeManager))]
32+
public AnyClass Value
33+
{
34+
get => new AnyClass();
35+
}
36+
}
37+
38+
[RegisterInstanceProvider]
39+
public class ProviderUsingFromWithContainerControlledLifetimeManager
40+
{
41+
[RegisterInstance(typeof(IAnyInterface), typeof(ContainerControlledLifetimeManager))]
42+
public AnyClass Value
43+
{
44+
get => new AnyClass();
45+
}
46+
}
47+
48+
[RegisterInstanceProvider]
49+
public class ProviderUsingFromWithExternallyControlledLifetimeManager
50+
{
51+
[RegisterInstance(typeof(IAnyInterface), typeof(ExternallyControlledLifetimeManager))]
52+
public AnyClass Value
53+
{
54+
get => new AnyClass();
55+
}
56+
}
57+
58+
[RegisterInstanceProvider]
59+
public class ProviderWithExternallyControlledLifetimeManager
60+
{
61+
[RegisterInstance(null, typeof(ExternallyControlledLifetimeManager))]
62+
public AnyClass Value
63+
{
64+
get => new AnyClass();
65+
}
66+
}
67+
68+
[RegisterInstanceProvider]
69+
public class ProviderWithLifetimemanagerWithoutInterface
70+
{
71+
[RegisterInstance(null, typeof(LifetimeManagerWithoutInterface))]
72+
public AnyClass Value
73+
{
74+
get => new AnyClass();
75+
}
76+
}
77+
78+
[RegisterInstanceProvider]
79+
public static class StaticClassWithAttribute
80+
{
81+
}
82+
83+
[RegisterInstanceProvider]
84+
public abstract class AbstractClassWithAttribute
85+
{
86+
}
87+
88+
public class AnyClass : IAnyInterface
89+
{
90+
}
91+
92+
public interface IAnyInterface
93+
{
94+
}
95+
96+
public class LifetimeManagerWithoutInterface
97+
{
98+
}
99+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
using NUnit.Framework;
6+
7+
using Unity;
8+
using Unity.Lifetime;
9+
10+
using UnityContainerAttributeRegistration;
11+
12+
using UnityContainerAttributeRegistrationTest.Assets.RegistertInstanceTestClasses;
13+
using UnityContainerAttributeRegistrationTest.Helper;
14+
15+
using static NUnit.Framework.Assert;
16+
17+
namespace UnityContainerAttributeRegistrationTest.Attribute
18+
{
19+
public class RegisterInstanceAttributeTest : TestBase
20+
{
21+
[Test]
22+
[TestCase(typeof(DefaultProvider), typeof(AnyClass), typeof(AnyClass), typeof(ContainerControlledLifetimeManager))]
23+
[TestCase(typeof(ProviderUsingFromWithoutLifetimeManager), typeof(IAnyInterface), typeof(AnyClass), typeof(ContainerControlledLifetimeManager))]
24+
[TestCase(typeof(ProviderUsingFromWithSingletonLifetimeManager), typeof(IAnyInterface), typeof(AnyClass), typeof(SingletonLifetimeManager))]
25+
[TestCase(typeof(ProviderUsingFromWithContainerControlledLifetimeManager), typeof(IAnyInterface), typeof(AnyClass), typeof(ContainerControlledLifetimeManager))]
26+
[TestCase(typeof(ProviderUsingFromWithExternallyControlledLifetimeManager), typeof(IAnyInterface), typeof(AnyClass), typeof(ExternallyControlledLifetimeManager))]
27+
[TestCase(typeof(ProviderWithExternallyControlledLifetimeManager), typeof(AnyClass), typeof(AnyClass), typeof(ExternallyControlledLifetimeManager))]
28+
public void TestPopulate(Type providerType, Type expectedFrom, Type expectedTo, Type expectedInstanceLifetimeManagerType)
29+
{
30+
Scope scope = new Scope();
31+
32+
scope.AddType(providerType);
33+
34+
IUnityContainer container = new UnityContainerPopulator(scope.GetAppDomain()).Populate();
35+
36+
IList<IContainerRegistration> result = container.Registrations.ToArray();
37+
38+
AreEqual(2, result.Count);
39+
IsTrue(IsUnityContainerRegistration(result[0]));
40+
IsTrue(IsExpectedRegisteredContainer(result[1], expectedFrom, expectedTo, expectedInstanceLifetimeManagerType));
41+
}
42+
43+
[Test]
44+
[TestCase(typeof(StaticClassWithAttribute))]
45+
[TestCase(typeof(AbstractClassWithAttribute))]
46+
[TestCase(typeof(ProviderWithLifetimemanagerWithoutInterface))]
47+
public void TestPopulate_InvalidUsage(Type providerType)
48+
{
49+
Scope scope = new Scope();
50+
51+
scope.AddType(providerType);
52+
53+
Throws<InvalidOperationException>(() => new UnityContainerPopulator(scope.GetAppDomain()).Populate());
54+
}
55+
56+
[Test]
57+
public void TestPopulate_WithCustomContainer()
58+
{
59+
Scope scope = new Scope();
60+
61+
IUnityContainer container = new UnityContainer();
62+
63+
IUnityContainer result = new UnityContainerPopulator(scope.GetAppDomain()).Populate(container);
64+
65+
AreSame(container, result);
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)