Skip to content

Commit c451e81

Browse files
authored
Merge pull request #10 from ManticSic/add-factory-attribute
Add factory attribute
2 parents 0a2a436 + 917cff9 commit c451e81

15 files changed

Lines changed: 640 additions & 113 deletions

File tree

README.md

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ namespace My.Awesome.App
3535

3636
### Register instances
3737

38-
You can register instances to an unity container using `UnityContainerAttributeRegistration.Attribute.RegisterInstanceProviderAttribute` and `UnityContainerAttributeRegistration.Attribute.RegisterInstanceAttribute`.
38+
You can register instances to an unity container using `UnityContainerAttributeRegistration.Attribute.RegisterProviderAttribute` and `UnityContainerAttributeRegistration.Attribute.RegisterInstanceAttribute`.
3939

40-
Classes marked with `UnityContainerAttributeRegistration.Attribute.RegisterInstanceProviderAttribute` will be instantiated using the container which should be populated with the instances.
40+
Classes marked with `UnityContainerAttributeRegistration.Attribute.RegisterProviderAttribute` will be instantiated using the container which should be populated with the instances.
4141
So you can use already registered services to create the instances, which should be later registered.
4242

4343
```
@@ -52,7 +52,7 @@ namespace My.Awesome.App
5252
}
5353
}
5454
55-
[RegisterInstanceProvider]
55+
[RegisterProvider]
5656
public class InstanceProvider
5757
{
5858
[RegisterInstance]
@@ -61,6 +61,45 @@ namespace My.Awesome.App
6161
}
6262
```
6363

64+
### Register Factory
65+
66+
You can register factory methods to an unity container using `UnityContainerAttributeRegistration.Attribute.RegisterProviderAttribute` and `UnityContainerAttributeRegistration.Attribute.RegisterFactoryAttribute`.
67+
68+
Classes marked with `UnityContainerAttributeRegistration.Attribute.RegisterProviderAttribute` will be instantiated using the container which should be populated with the instances.
69+
So you can use already registered services to create the instances, which should be later registered.
70+
71+
Its only important to have the right parameters (see example).
72+
73+
```
74+
namespace My.Awesome.App
75+
{
76+
public class Program
77+
{
78+
public static void Main(string[] args)
79+
{
80+
UnityContainerPopulator populator = new UnityContainerPopulator();
81+
IUnityContainer container = populator.Populate();
82+
}
83+
}
84+
85+
[RegisterProvider]
86+
public class InstanceProvider
87+
{
88+
[RegisterFactory]
89+
public MyClass Factory(IUnityContainer container)
90+
{
91+
// do some magic
92+
}
93+
94+
[RegisterFactory]
95+
public MyClass Factory(IUnityContainer container, Type typeValue, string stringvalue)
96+
{
97+
// do some magic
98+
}
99+
}
100+
}
101+
```
102+
64103
### Using a custom container
65104

66105
It is possible to populate a already created container.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
3+
using JetBrains.Annotations;
4+
5+
6+
namespace UnityContainerAttributeRegistration.Attribute
7+
{
8+
/// <summary>
9+
/// Mark a method to be registered as factory to an <see cref="Unity.IUnityContainer" />
10+
/// </summary>
11+
[AttributeUsage(AttributeTargets.Method)]
12+
public class RegisterFactoryAttribute : System.Attribute
13+
{
14+
/// <summary>
15+
/// Candidate for registration to <see cref="Unity" />.
16+
/// </summary>
17+
public RegisterFactoryAttribute([CanBeNull] Type from = null,
18+
[CanBeNull] Type lifetimeManager = null)
19+
{
20+
From = from;
21+
LifetimeManager = lifetimeManager;
22+
}
23+
24+
/// <summary>
25+
/// <see cref="Type" /> that will be requested.
26+
/// </summary>
27+
[CanBeNull]
28+
internal Type From { get; }
29+
30+
/// <summary>
31+
/// The <see cref="LifetimeManager" /> that controls the lifetime
32+
/// of the returned instance.
33+
/// </summary>
34+
[CanBeNull]
35+
internal Type LifetimeManager { get; }
36+
}
37+
}

src/UnityContainerAttributeRegistration/Attribute/RegisterInstanceProviderAttribute.cs renamed to src/UnityContainerAttributeRegistration/Attribute/RegisterProviderAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
namespace UnityContainerAttributeRegistration.Attribute
55
{
66
/// <summary>
7-
/// Mark a type to be a provider for <see cref="RegisterInstanceAttribute" />
7+
/// Mark a type to be a provider for <see cref="RegisterInstanceAttribute" /> and <see cref="RegisterFactoryAttribute" />
88
/// </summary>
99
[AttributeUsage(AttributeTargets.Class)]
10-
public class RegisterInstanceProviderAttribute : System.Attribute
10+
public class RegisterProviderAttribute : System.Attribute
1111
{
1212
}
1313
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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.Attribute;
12+
13+
14+
namespace UnityContainerAttributeRegistration.Populator
15+
{
16+
/// <summary>
17+
/// Populator for the <see cref="UnityContainerAttributeRegistration.Attribute.RegisterFactoryAttribute" />.
18+
/// </summary>
19+
internal class FactoryPopulator : Populator
20+
{
21+
/// <inheritdoc cref="Populator.Populate" />
22+
/// <exception cref="InvalidOperationException">Invalid factory method.</exception>
23+
public override IUnityContainer Populate(IUnityContainer container, IList<Type> typesWithAttribute)
24+
{
25+
IEnumerable<FactoryToRegister> factoriesToRegister =
26+
typesWithAttribute.SelectMany(providerClassType => GetFactoryToRegisterFor(container, providerClassType));
27+
28+
foreach(FactoryToRegister factoryToRegister in factoriesToRegister)
29+
{
30+
Type returnType = factoryToRegister.ReturnType;
31+
Func<IUnityContainer, Type, string, object> factory = factoryToRegister.Factory;
32+
IFactoryLifetimeManager lifetimeManager = factoryToRegister.LifetimeManager;
33+
34+
container.RegisterFactory(returnType, factory, lifetimeManager);
35+
}
36+
37+
return container;
38+
}
39+
40+
/// <summary>
41+
/// Get all candidates to register.
42+
/// </summary>
43+
/// <param name="container"><see cref="IUnityContainer" /> to instantiate the provider class</param>
44+
/// <param name="providerClassType">Factory provider class</param>
45+
/// <returns>List of candidates to register</returns>
46+
/// <exception cref="InvalidOperationException">Invalid factory method.</exception>
47+
private IEnumerable<FactoryToRegister> GetFactoryToRegisterFor(IUnityContainer container, Type providerClassType)
48+
{
49+
object providerClassInstance = container.Resolve(providerClassType);
50+
MethodInfo[] methodInfos = providerClassType.GetMethods();
51+
52+
return methodInfos
53+
.Where(info => info.CustomAttributes.Any(data => data.AttributeType == typeof(RegisterFactoryAttribute)))
54+
.Select(info => CreateFactoryToRegisterFrom(info, providerClassInstance))
55+
.ToList();
56+
}
57+
58+
/// <summary>
59+
/// Transform a <see cref="MethodInfo" /> and an instance to a <see cref="FactoryToRegister" /> object.
60+
/// </summary>
61+
/// <param name="info"><see cref="MethodInfo" /> of the factory method.</param>
62+
/// <param name="instance">Instance of the factory method.</param>
63+
/// <returns>Wrapper for easy registration.</returns>
64+
/// <exception cref="InvalidOperationException">Invalid factory method.</exception>
65+
private FactoryToRegister CreateFactoryToRegisterFrom(MethodInfo info, object instance)
66+
{
67+
RegisterFactoryAttribute attribute = info.GetCustomAttribute<RegisterFactoryAttribute>();
68+
Type returnType = attribute.From ?? info.ReturnType;
69+
70+
if(returnType == typeof(void))
71+
{
72+
throw new InvalidOperationException("Return type must not be void.");
73+
}
74+
75+
if(!IsUnityFactorySignature(info))
76+
{
77+
throw new InvalidOperationException("Factory method signature does not match.");
78+
}
79+
80+
IFactoryLifetimeManager lifetimeManager =
81+
attribute.LifetimeManager == null
82+
? null
83+
: GetInstanceByType<IFactoryLifetimeManager>(attribute.LifetimeManager);
84+
85+
return new FactoryToRegister(returnType, GetFactoryMethodFor(info, instance), lifetimeManager);
86+
}
87+
88+
/// <summary>
89+
/// Verify that the parameters matches the requirements of Unity
90+
/// </summary>
91+
/// <param name="methodInfo"><see cref="MethodInfo" /> to check.</param>
92+
/// <returns>Whether <paramref name="methodInfo" /> matches the requirements or not.</returns>
93+
private bool IsUnityFactorySignature(MethodInfo methodInfo)
94+
{
95+
ParameterInfo[] parameters = methodInfo.GetParameters();
96+
97+
switch(parameters.Length)
98+
{
99+
case 1 when parameters[0]
100+
.ParameterType == typeof(IUnityContainer):
101+
case 3 when parameters[0]
102+
.ParameterType == typeof(IUnityContainer) && parameters[1]
103+
.ParameterType == typeof(Type) && parameters[2]
104+
.ParameterType == typeof(string):
105+
{
106+
return true;
107+
}
108+
default:
109+
{
110+
return false;
111+
}
112+
}
113+
}
114+
115+
/// <summary>
116+
/// Create a wrapper function for a factory method.
117+
/// </summary>
118+
/// <param name="methodInfo"><see cref="MethodInfo" /> of the factory method.</param>
119+
/// <param name="instance">Instance of the factory method.</param>
120+
/// <returns><see cref="Func{TResult}" /> to call the factory method.</returns>
121+
private Func<IUnityContainer, Type, string, object> GetFactoryMethodFor(MethodInfo methodInfo, object instance)
122+
{
123+
return (container, typeValue, stringValue) =>
124+
{
125+
IList<object> invokeParams = new List<object> {container};
126+
127+
if(methodInfo.GetParameters()
128+
.Length == 3)
129+
{
130+
invokeParams.Add(typeValue);
131+
invokeParams.Add(stringValue);
132+
}
133+
134+
return methodInfo.Invoke(instance, invokeParams.ToArray());
135+
};
136+
}
137+
138+
/// <summary>
139+
/// Wrapper to register factories
140+
/// </summary>
141+
private sealed class FactoryToRegister
142+
{
143+
public FactoryToRegister([NotNull] Type returnType,
144+
[NotNull] Func<IUnityContainer, Type, string, object> factory,
145+
[CanBeNull] IFactoryLifetimeManager lifetimeManager)
146+
{
147+
ReturnType = returnType;
148+
Factory = factory;
149+
LifetimeManager = lifetimeManager;
150+
}
151+
152+
/// <summary>
153+
/// Requested type as return type of the factory
154+
/// </summary>
155+
[NotNull]
156+
public Type ReturnType { get; }
157+
158+
/// <summary>
159+
/// Factory method
160+
/// </summary>
161+
[NotNull]
162+
public Func<IUnityContainer, Type, string, object> Factory { get; }
163+
164+
/// <summary>
165+
/// Used lifetime manager
166+
/// </summary>
167+
[CanBeNull]
168+
public IFactoryLifetimeManager LifetimeManager { get; }
169+
}
170+
}
171+
}

src/UnityContainerAttributeRegistration/Populator/IPopulator.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using Unity;
1+
using System;
2+
using System.Collections.Generic;
3+
4+
using Unity;
25

36

47
namespace UnityContainerAttributeRegistration.Populator
@@ -12,7 +15,8 @@ internal interface IPopulator
1215
/// Populate the passed <paramref name="container" />.
1316
/// </summary>
1417
/// <param name="container"><see cref="IUnityContainer" /> to populate.</param>
18+
/// <param name="typesWith"></param>
1519
/// <returns>Passed <paramref name="container" />.</returns>
16-
IUnityContainer Populate(IUnityContainer container);
20+
IUnityContainer Populate(IUnityContainer container, IList<Type> typesWith);
1721
}
1822
}

src/UnityContainerAttributeRegistration/Populator/InstancePopulator.cs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
using Unity;
99
using Unity.Lifetime;
1010

11-
using UnityContainerAttributeRegistration.Provider;
1211
using UnityContainerAttributeRegistration.Attribute;
13-
using UnityContainerAttributeRegistration.Exention;
1412

1513

1614
namespace UnityContainerAttributeRegistration.Populator
@@ -20,25 +18,10 @@ namespace UnityContainerAttributeRegistration.Populator
2018
/// </summary>
2119
internal class InstancePopulator : Populator
2220
{
23-
/// <summary>
24-
/// ctor
25-
/// </summary>
26-
/// <param name="appDomain">Used <see cref="IAssemblyProvider" /> for searching for candidates.</param>
27-
public InstancePopulator(IAssemblyProvider appDomain) : base(appDomain)
28-
{
29-
}
30-
31-
/// <summary>
32-
/// Populate the passed <paramref name="container" />.
33-
/// </summary>
34-
/// <param name="container"><see cref="IUnityContainer" /> to populate.</param>
35-
/// <returns>Passed <paramref name="container" />.</returns>
21+
/// <inheritdoc cref="Populator.Populate" />
3622
/// <exception cref="InvalidOperationException">Class type must not be static or abstract.</exception>
37-
public override IUnityContainer Populate(IUnityContainer container)
23+
public override IUnityContainer Populate(IUnityContainer container, IList<Type> typesWithAttribute)
3824
{
39-
IList<Type> typesWithAttribute = GetTypesWith<RegisterInstanceProviderAttribute>(TypeDefined.Inherit)
40-
.ToList();
41-
4225
IEnumerable<InstanceToRegister> instancesToRegister =
4326
typesWithAttribute.SelectMany(providerClassType => GetInstancesToRegisterFor(container, providerClassType));
4427

@@ -54,20 +37,14 @@ public override IUnityContainer Populate(IUnityContainer container)
5437
}
5538

5639
/// <summary>
57-
/// Create a list of <see cref="InstanceToRegister" /> depending on class marked with <see cref="RegisterInstanceProviderAttribute" />
40+
/// Create a list of <see cref="InstanceToRegister" /> depending on class marked with <see cref="RegisterProviderAttribute" />
5841
/// </summary>
5942
/// <param name="container"><see cref="IUnityContainer" /> to resolve <paramref name="providerClassType" /></param>
6043
/// <param name="providerClassType">Class type used to search for <see cref="RegisterInstanceAttribute" /></param>
6144
/// <returns>List of instances to register with all needed parameters</returns>
6245
/// <exception cref="InvalidOperationException"><paramref name="providerClassType" /> type must not be static or abstract.</exception>
6346
private IEnumerable<InstanceToRegister> GetInstancesToRegisterFor(IUnityContainer container, Type providerClassType)
6447
{
65-
if(providerClassType.IsStatic() || providerClassType.IsAbstract)
66-
{
67-
throw new InvalidOperationException(
68-
$"Class type must not be static or abstract to be used with RegisterTypeAttribute: {providerClassType.FullName}");
69-
}
70-
7148
object providerClassInstance = container.Resolve(providerClassType);
7249
PropertyInfo[] properties = providerClassType.GetProperties();
7350

@@ -89,7 +66,7 @@ private IEnumerable<InstanceToRegister> GetInstancesToRegisterFor(IUnityContaine
8966
}
9067

9168
/// <summary>
92-
/// Wrapper to regsiter isntances
69+
/// Wrapper to register isntances
9370
/// </summary>
9471
private sealed class InstanceToRegister
9572
{

0 commit comments

Comments
 (0)