Skip to content

Commit 577235a

Browse files
committed
Resolves #1
1 parent dbccf48 commit 577235a

File tree

6 files changed

+274
-297
lines changed

6 files changed

+274
-297
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Xer.Cqrs.CommandStack.Extensions.Attributes
9+
{
10+
public partial class CommandHandlerAttributeMethod
11+
{
12+
#region Factory Methods
13+
14+
/// <summary>
15+
/// Create CommandHandlerAttributeMethod from the method info.
16+
/// </summary>
17+
/// <param name="methodInfo">Method info that has CommandHandlerAttribute custom attribute.</param>
18+
/// <param name="instanceFactory">Factory delegate that provides an instance of the method info's declaring type.</param>
19+
/// <returns>Instance of CommandHandlerAttributeMethod.</returns>
20+
public static CommandHandlerAttributeMethod FromMethodInfo(MethodInfo methodInfo, Func<object> instanceFactory)
21+
{
22+
Type commandType;
23+
bool isAsyncMethod;
24+
25+
if (methodInfo == null)
26+
{
27+
throw new ArgumentNullException(nameof(methodInfo));
28+
}
29+
30+
if (!IsValid(methodInfo))
31+
{
32+
throw new InvalidOperationException($"Method is not marked with [CommandHandler] attribute. {createCheckMethodMessage(methodInfo)}.");
33+
}
34+
35+
// Get all method parameters.
36+
ParameterInfo[] methodParameters = methodInfo.GetParameters();
37+
38+
// Get first method parameter that is a class (not struct). This assumes that the first parameter is the command.
39+
ParameterInfo commandParameter = methodParameters.FirstOrDefault();
40+
if (commandParameter != null)
41+
{
42+
// Check if parameter is a class.
43+
if (!commandParameter.ParameterType.GetTypeInfo().IsClass)
44+
{
45+
throw new InvalidOperationException($"Method's command parameter is not a reference type, only reference type commands are supported. {createCheckMethodMessage(methodInfo)}.");
46+
}
47+
48+
// Set command type.
49+
commandType = commandParameter.ParameterType;
50+
}
51+
else
52+
{
53+
// Method has no parameter.
54+
throw new InvalidOperationException($"Method must accept a command object as a parameter. {createCheckMethodMessage(methodInfo)}.");
55+
}
56+
57+
// Only valid return types are Task/void.
58+
if (methodInfo.ReturnType == typeof(Task))
59+
{
60+
isAsyncMethod = true;
61+
}
62+
else if (methodInfo.ReturnType == typeof(void))
63+
{
64+
isAsyncMethod = false;
65+
66+
// if(methodInfo.CustomAttributes.Any(p => p.AttributeType == typeof(AsyncStateMachineAttribute)))
67+
// {
68+
// throw new InvalidOperationException($"Methods with async void signatures are not allowed. A Task may be used as return type instead of void. Check method: {methodInfo.ToString()}.");
69+
// }
70+
}
71+
else
72+
{
73+
// Return type is not Task/void. Invalid.
74+
throw new InvalidOperationException($"Method marked with [CommandHandler] can only have void or a Task as return value. {createCheckMethodMessage(methodInfo)}.");
75+
}
76+
77+
bool supportsCancellation = methodParameters.Any(p => p.ParameterType == typeof(CancellationToken));
78+
79+
if (!isAsyncMethod && supportsCancellation)
80+
{
81+
throw new InvalidOperationException($"Cancellation token support is only available for async methods (methods returning a Task). {createCheckMethodMessage(methodInfo)}.");
82+
}
83+
84+
return new CommandHandlerAttributeMethod(methodInfo, commandType, instanceFactory, isAsyncMethod, supportsCancellation);
85+
86+
// Local function.
87+
string createCheckMethodMessage(MethodInfo method) => $"Check {methodInfo.DeclaringType.Name}'s {methodInfo.ToString()} method";
88+
}
89+
90+
/// <summary>
91+
/// Create CommandHandlerAttributeMethod from the method info.
92+
/// </summary>
93+
/// <param name="methodInfos">Method infos that have CommandHandlerAttribute custom attributes.</param>
94+
/// <param name="instanceFactory">Factory delegate that provides an instance of a method info's declaring type.</param>
95+
/// <returns>Instances of CommandHandlerAttributeMethod.</returns>
96+
public static IEnumerable<CommandHandlerAttributeMethod> FromMethodInfos(IEnumerable<MethodInfo> methodInfos, Func<Type, object> instanceFactory)
97+
{
98+
if (methodInfos == null)
99+
{
100+
throw new ArgumentNullException(nameof(methodInfos));
101+
}
102+
103+
return methodInfos.Select(m => FromMethodInfo(m, () => instanceFactory.Invoke(m.DeclaringType)));
104+
}
105+
106+
/// <summary>
107+
/// Detect methods marked with [CommandHandler] attribute and translate to CommandHandlerAttributeMethod instances.
108+
/// </summary>
109+
/// <typeparam name="T">Type to scan for methods marked with the [CommandHandler] attribute.</typeparam>
110+
/// <param name="instanceFactory">Factory delegate that provides an instance of the specified type.</param>
111+
/// <returns>List of all CommandHandlerAttributeMethod detected.</returns>
112+
public static IEnumerable<CommandHandlerAttributeMethod> FromType<T>(Func<T> instanceFactory) where T : class
113+
{
114+
return FromType(typeof(T), instanceFactory);
115+
}
116+
117+
/// <summary>
118+
/// Detect methods marked with [CommandHandler] attribute and translate to CommandHandlerAttributeMethod instances.
119+
/// </summary>
120+
/// <param name="type">Type to scan for methods marked with the [CommandHandler] attribute.</param>
121+
/// <param name="instanceFactory">Factory delegate that provides an instance of the specified type.</param>
122+
/// <returns>List of all CommandHandlerAttributeMethod detected.</returns>
123+
public static IEnumerable<CommandHandlerAttributeMethod> FromType(Type type, Func<object> instanceFactory)
124+
{
125+
if (type == null)
126+
{
127+
throw new ArgumentNullException(nameof(type));
128+
}
129+
130+
IEnumerable<MethodInfo> methods = type.GetTypeInfo().DeclaredMethods.Where(m => IsValid(m));
131+
132+
return FromMethodInfos(methods, _ => instanceFactory.Invoke());
133+
}
134+
135+
/// <summary>
136+
/// Detect methods marked with [CommandHandler] attribute and translate to CommandHandlerAttributeMethod instances.
137+
/// </summary>
138+
/// <param name="types">Types to scan for methods marked with the [CommandHandler] attribute.</param>
139+
/// <param name="instanceFactory">Factory delegate that provides an instance of a given type.</param>
140+
/// <returns>List of all CommandHandlerAttributeMethod detected.</returns>
141+
public static IEnumerable<CommandHandlerAttributeMethod> FromTypes(IEnumerable<Type> types, Func<Type, object> instanceFactory)
142+
{
143+
if (types == null)
144+
{
145+
throw new ArgumentNullException(nameof(types));
146+
}
147+
148+
return types.SelectMany(type => FromType(type, () => instanceFactory.Invoke(type)));
149+
}
150+
151+
/// <summary>
152+
/// Detect methods marked with [CommandHandler] attribute and translate to CommandHandlerAttributeMethod instances.
153+
/// </summary>
154+
/// <param name="commandHandlerAssembly">Assembly to scan for methods marked with the [CommandHandler] attribute.</param>
155+
/// <param name="instanceFactory">Factory delegate that provides an instance of a type that has methods marked with [CommandHandler] attribute.</param>
156+
/// <returns>List of all CommandHandlerAttributeMethod detected.</returns>
157+
public static IEnumerable<CommandHandlerAttributeMethod> FromAssembly(Assembly commandHandlerAssembly, Func<Type, object> instanceFactory)
158+
{
159+
if (commandHandlerAssembly == null)
160+
{
161+
throw new ArgumentNullException(nameof(commandHandlerAssembly));
162+
}
163+
164+
IEnumerable<MethodInfo> commandHandlerMethods = commandHandlerAssembly.DefinedTypes
165+
.Where(typeInfo => IsFoundInType(typeInfo))
166+
.SelectMany(typeInfo => typeInfo.DeclaredMethods.Where(method =>
167+
IsValid(method)));
168+
169+
return FromMethodInfos(commandHandlerMethods, instanceFactory);
170+
}
171+
172+
/// <summary>
173+
/// Detect methods marked with [CommandHandler] attribute and translate to CommandHandlerAttributeMethod instances.
174+
/// </summary>
175+
/// <param name="commandHandlerAssemblies">Assemblies to scan for methods marked with the [CommandHandler] attribute.</param>
176+
/// <param name="instanceFactory">Factory delegate that provides an instance of a type that has methods marked with [CommandHandler] attribute.</param>
177+
/// <returns>List of all CommandHandlerAttributeMethod detected.</returns>
178+
public static IEnumerable<CommandHandlerAttributeMethod> FromAssemblies(IEnumerable<Assembly> commandHandlerAssemblies, Func<Type, object> instanceFactory)
179+
{
180+
if (commandHandlerAssemblies == null)
181+
{
182+
throw new ArgumentNullException(nameof(commandHandlerAssemblies));
183+
}
184+
185+
return commandHandlerAssemblies.SelectMany(assembly => FromAssembly(assembly, instanceFactory));
186+
}
187+
188+
#endregion Factory Methods
189+
}
190+
}

0 commit comments

Comments
 (0)