Skip to content

Commit

Permalink
Support KeyedService auto-injection in business object factory.
Browse files Browse the repository at this point in the history
  • Loading branch information
Codespilot committed Sep 22, 2024
1 parent 1dce34b commit e7a3cd1
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 97 deletions.
49 changes: 25 additions & 24 deletions Source/Euonia.Business/Factory/BusinessObjectFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ public BusinessObjectFactory(IServiceProvider provider, IObjectActivator activat
/// <inheritdoc/>
public async Task<TTarget> CreateAsync<TTarget>(params object[] criteria)
{
criteria ??= new object[] { null };
criteria ??= [null];
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryCreateAttribute>(criteria);
var target = GetObjectInstance<TTarget>();
if (target is IEditableObject editable)
{
editable.MarkAsInsert();
}

try
{
_activator?.InitializeInstance(target);
Expand All @@ -56,7 +57,7 @@ public async Task<TTarget> CreateAsync<TTarget>(params object[] criteria)
/// <inheritdoc/>
public async Task<TTarget> FetchAsync<TTarget>(params object[] criteria)
{
criteria ??= new object[] { null };
criteria ??= [null];
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryFetchAttribute>(criteria);
var target = GetObjectInstance<TTarget>();
try
Expand All @@ -74,7 +75,7 @@ public async Task<TTarget> FetchAsync<TTarget>(params object[] criteria)
/// <inheritdoc/>
public async Task<TTarget> InsertAsync<TTarget>(params object[] criteria)
{
criteria ??= new object[] { null };
criteria ??= [null];
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryInsertAttribute>(criteria);
var target = GetObjectInstance<TTarget>();
try
Expand All @@ -92,7 +93,7 @@ public async Task<TTarget> InsertAsync<TTarget>(params object[] criteria)
/// <inheritdoc/>
public async Task<TTarget> UpdateAsync<TTarget>(params object[] criteria)
{
criteria ??= new object[] { null };
criteria ??= [null];
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryUpdateAttribute>(criteria);
var target = GetObjectInstance<TTarget>();
try
Expand All @@ -114,18 +115,18 @@ public async Task<TTarget> SaveAsync<TTarget>(TTarget target, CancellationToken
{
IEditableObject editableObject => editableObject.State switch
{
ObjectEditState.Insert => ObjectReflector.FindFactoryMethod<TTarget, FactoryInsertAttribute>(new object[] { cancellationToken }),
ObjectEditState.Update => ObjectReflector.FindFactoryMethod<TTarget, FactoryUpdateAttribute>(new object[] { cancellationToken }),
ObjectEditState.Delete => ObjectReflector.FindFactoryMethod<TTarget, FactoryDeleteAttribute>(new object[] { cancellationToken }),
ObjectEditState.Insert => ObjectReflector.FindFactoryMethod<TTarget, FactoryInsertAttribute>([cancellationToken]),
ObjectEditState.Update => ObjectReflector.FindFactoryMethod<TTarget, FactoryUpdateAttribute>([cancellationToken]),
ObjectEditState.Delete => ObjectReflector.FindFactoryMethod<TTarget, FactoryDeleteAttribute>([cancellationToken]),
ObjectEditState.None => throw new InvalidOperationException(),
_ => throw new ArgumentOutOfRangeException(nameof(target), Resources.IDS_INVALID_STATE)
},
ICommandObject => ObjectReflector.FindFactoryMethod<TTarget, FactoryExecuteAttribute>(new object[] { cancellationToken }),
ICommandObject => ObjectReflector.FindFactoryMethod<TTarget, FactoryExecuteAttribute>([cancellationToken]),
IReadOnlyObject => throw new InvalidOperationException("The operation can not apply for ReadOnlyObject."),
_ => ObjectReflector.FindFactoryMethod<TTarget, FactoryUpdateAttribute>(new object[] { cancellationToken })
_ => ObjectReflector.FindFactoryMethod<TTarget, FactoryUpdateAttribute>([cancellationToken])
};

await InvokeAsync(method, target, new object[] { cancellationToken });
await InvokeAsync(method, target, [cancellationToken]);

return target;
}
Expand All @@ -134,12 +135,12 @@ public async Task<TTarget> SaveAsync<TTarget>(TTarget target, CancellationToken
public async Task<TTarget> ExecuteAsync<TTarget>(TTarget target, CancellationToken cancellationToken = default)
where TTarget : ICommandObject
{
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryExecuteAttribute>(new object[] { cancellationToken });
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryExecuteAttribute>([cancellationToken]);

try
{
_activator?.InitializeInstance(target);
await InvokeAsync(method, target, new object[] { cancellationToken });
await InvokeAsync(method, target, [cancellationToken]);
return target;
}
finally
Expand All @@ -152,7 +153,7 @@ public async Task<TTarget> ExecuteAsync<TTarget>(TTarget target, CancellationTok
public async Task<TTarget> ExecuteAsync<TTarget>(params object[] criteria)
where TTarget : ICommandObject
{
criteria ??= new object[] { null };
criteria ??= [null];
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryExecuteAttribute>(criteria);
var target = GetObjectInstance<TTarget>();

Expand All @@ -171,7 +172,7 @@ public async Task<TTarget> ExecuteAsync<TTarget>(params object[] criteria)
/// <inheritdoc/>
public async Task DeleteAsync<TTarget>(params object[] criteria)
{
criteria ??= new object[] { null };
criteria ??= [null];
var method = ObjectReflector.FindFactoryMethod<TTarget, FactoryDeleteAttribute>(criteria);
var target = GetObjectInstance<TTarget>();

Expand Down Expand Up @@ -208,28 +209,28 @@ private static async Task InvokeAsync<TTarget>(MethodInfo method, TTarget target
private TTarget GetObjectInstance<TTarget>()
{
var @object = ActivatorUtilities.GetServiceOrCreateInstance<TTarget>(_provider);
if (@object is IUseBusinessContext ctx)
{
ctx.BusinessContext = _provider.GetRequiredService<BusinessContext>();
}

if (@object is IHasLazyServiceProvider lazy)
switch (@object)
{
lazy.LazyServiceProvider = _provider.GetRequiredService<ILazyServiceProvider>();
case IUseBusinessContext ctx:
ctx.BusinessContext = _provider.GetRequiredService<BusinessContext>();
break;
case IHasLazyServiceProvider lazy:
lazy.LazyServiceProvider = _provider.GetRequiredService<ILazyServiceProvider>();
break;
}

var properties = ObjectReflector.GetAutoInjectProperties(typeof(TTarget));

foreach (var (property, type, multiple) in properties)
foreach (var (property, type, multiple, serviceKey) in properties)
{
if (multiple)
{
var implement = _provider.GetServices(type);
var implement = serviceKey == null ? _provider.GetServices(type) : _provider.GetKeyedServices(type, serviceKey);
property.SetValue(@object, implement);
}
else
{
var implement = _provider.GetService(type);
var implement = serviceKey == null ? _provider.GetService(type) : _provider.GetKeyedService(type, serviceKey);
property.SetValue(@object, implement);
}
}
Expand Down
138 changes: 65 additions & 73 deletions Source/Euonia.Business/Factory/ObjectReflector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,34 @@ public class ObjectReflector
private const BindingFlags BINDING_FLAGS = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;

private static readonly string[] _collectionTypesName =
{
[
typeof(IList<>).FullName,
typeof(ICollection<>).FullName,
typeof(IEnumerable<>).FullName,
};
typeof(IEnumerable<>).FullName
];

private static readonly ConcurrentDictionary<Type, List<Tuple<PropertyInfo, Type, bool>>> _propertyCache = new();
private static readonly ConcurrentDictionary<Type, List<Tuple<PropertyInfo, Type, bool, object>>> _propertyCache = new();
private static readonly ConcurrentDictionary<string, MethodInfo> _factoryMethods = new();

internal static List<Tuple<PropertyInfo, Type, bool>> GetAutoInjectProperties(Type objectType)
internal static List<Tuple<PropertyInfo, Type, bool, object>> GetAutoInjectProperties(Type objectType)
{
return _propertyCache.GetOrAdd(objectType, type =>
{
var autoInjectProperties = new List<Tuple<PropertyInfo, Type, bool>>();
var autoInjectProperties = new List<Tuple<PropertyInfo, Type, bool, object>>();
var propertiesOfType = type.GetRuntimeProperties().ToList();

foreach (var property in propertiesOfType)
{
if (property.GetCustomAttribute<InjectAttribute>() == null)
var attribute = property.GetCustomAttribute<InjectAttribute>();

if (attribute == null)
{
continue;
}

var (propertyType, multiple) = FindServiceType(property.Name, property.PropertyType);

autoInjectProperties.Add(Tuple.Create(property, propertyType, multiple));
autoInjectProperties.Add(Tuple.Create(property, propertyType, multiple, attribute.ServiceKey));
}

return autoInjectProperties;
Expand All @@ -51,20 +53,6 @@ internal static MethodInfo FindFactoryMethod<TTarget, TAttribute>(object[] crite

internal static MethodInfo FindFactoryMethod<TTarget>(Type attributeType, object[] criteria)
{
// Type[] types;
// if (criteria != null)
// {
// types = new Type[criteria.Length];
// for (var index = 0; index < criteria.Length; index++)
// {
// types[index] = criteria[index].GetType();
// }
// }
// else
// {
// types = null;
// }
//
var name = GetMethodName<TTarget>(attributeType, criteria);
return _factoryMethods.GetOrAdd(name, () => FindMatchedMethod<TTarget>(attributeType, criteria));
}
Expand All @@ -82,14 +70,7 @@ private static MethodInfo FindMatchedMethod<TTarget>(Type attributeType, object[
int parameterCount;
if (criteria != null)
{
if (criteria.GetType() == typeof(object[]))
{
parameterCount = criteria.GetLength(0);
}
else
{
parameterCount = 1;
}
parameterCount = criteria.GetType() == typeof(object[]) ? criteria.GetLength(0) : 1;
}
else
{
Expand Down Expand Up @@ -157,7 +138,7 @@ private static MethodInfo FindMatchedMethod<TTarget>(Type attributeType, object[
{
var lastParam = method.GetParameters().LastOrDefault();
if (lastParam != null && lastParam.ParameterType == typeof(object[]) &&
lastParam.GetCustomAttributes<ParamArrayAttribute>().Any())
lastParam.GetCustomAttributes<ParamArrayAttribute>().Any())
{
matches.Add(Tuple.Create(method, 1 + score));
}
Expand Down Expand Up @@ -213,7 +194,7 @@ private static MethodInfo FindMatchedMethod<TTarget>(Type attributeType, object[
public static MethodInfo FindMatchedMethod<TTarget>(Type attributeType, IReadOnlyList<Type> parameterTypes)
{
var methods = typeof(TTarget).GetRuntimeMethods()
.Where(t => t.GetCustomAttribute(attributeType) != null);
.Where(t => t.GetCustomAttribute(attributeType) != null);
if (methods == null || !methods.Any())
{
throw new MissingMethodException($"Missing method with attribute '{attributeType.Name}' on {typeof(TTarget).FullName}");
Expand Down Expand Up @@ -257,7 +238,7 @@ private static List<Tuple<MethodInfo, int>> GetCandidateMethods(Type targetType,

var result = new List<Tuple<MethodInfo, int>>();
var methods = targetType.GetMethods(BINDING_FLAGS)
.Where(t => t.GetCustomAttribute(attributeType) != null || validNames.Contains(t.Name));
.Where(t => t.GetCustomAttribute(attributeType) != null || validNames.Contains(t.Name));

// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var method in methods)
Expand Down Expand Up @@ -288,60 +269,71 @@ private static List<Tuple<MethodInfo, int>> GetCandidateMethods(Type targetType,
/// <exception cref="InvalidOperationException"></exception>
private static Tuple<Type, bool> FindServiceType(string name, Type type, bool? multiple = null)
{
if (type.IsPrimitive)
{
throw new NotSupportedException("Can not inject primitive type property.");
}

if (!type.IsClass && !type.IsInterface)
{
throw new NotSupportedException($"Can not inject property '{name}', the property type {type.FullName} is not supported.");
}

if (type == typeof(object))
while (true)
{
throw new NotSupportedException($"Can not inject property '{name}', the property type {type.FullName} is not supported.");
}
if (type.IsPrimitive)
{
throw new NotSupportedException("Can not inject primitive type property.");
}

var @interface = type.GetInterface("IEnumerable");
if (@interface == null)
{
return Tuple.Create(type, multiple ?? false);
}
if (!type.IsClass && !type.IsInterface)
{
throw new NotSupportedException($"Can not inject property '{name}', the property type {type.FullName} is not supported.");
}

if (multiple == true)
{
throw new NotSupportedException();
}
if (type == typeof(object))
{
throw new NotSupportedException($"Can not inject property '{name}', the property type {type.FullName} is not supported.");
}

if (type.IsArray)
{
var interfaces = type.FindInterfaces(HandlerInterfaceFilter, null);
if (interfaces == null || interfaces.Length == 0)
var @interface = type.GetInterface("IEnumerable");
if (@interface == null)
{
throw new InvalidOperationException();
return Tuple.Create(type, multiple ?? false);
}

return FindServiceType(name, interfaces[0].GenericTypeArguments[0], true);
}
if (multiple == true)
{
throw new NotSupportedException();
}

if (type.IsGenericType)
{
var propertyTypeFullname = $"{type.Namespace}.{type.Name}";
if (propertyTypeFullname == typeof(IEnumerable<>).FullName)
if (type.IsArray)
{
if (type.GenericTypeArguments.Length != 1)
var interfaces = type.FindInterfaces(HandlerInterfaceFilter, null);
if (interfaces == null || interfaces.Length == 0)
{
throw new InvalidOperationException("");
throw new InvalidOperationException();
}

var genericArgumentType = type.GenericTypeArguments[0];
type = interfaces[0].GenericTypeArguments[0];
multiple = true;
continue;
}

if (type.IsGenericType)
{
var propertyTypeFullname = $"{type.Namespace}.{type.Name}";
if (propertyTypeFullname == typeof(IEnumerable<>).FullName)
{
if (type.GenericTypeArguments.Length != 1)
{
throw new InvalidOperationException("");
}

var genericArgumentType = type.GenericTypeArguments[0];

return FindServiceType(name, genericArgumentType, true);
type = genericArgumentType;
multiple = true;
continue;
}
}

{
// Prevent code inspection warning.
}
}

throw new NotSupportedException($"Can not inject property '{name}', the property type {type.FullName} is not supported.");
throw new NotSupportedException($"Can not inject property '{name}', the property type {type.FullName} is not supported.");
}
}

private static bool HandlerInterfaceFilter(Type type, object criteria)
Expand Down Expand Up @@ -488,7 +480,7 @@ private static int CalculateParameterMatchScore(ParameterInfo parameter, object
private static string[] GetConventionalMethodNames(Type attributeType)
{
var validNames = new[]
{
{
$"Factory{attributeType.Name.Replace(nameof(Attribute), string.Empty)}",
$"Factory{attributeType.Name.Replace(nameof(Attribute), string.Empty)}Async",
$"{attributeType.Name.Replace(nameof(Attribute), string.Empty)}",
Expand Down

0 comments on commit e7a3cd1

Please sign in to comment.