From 70cdcf3a86591ee132400c593b63828111d40178 Mon Sep 17 00:00:00 2001 From: damon Date: Fri, 13 Sep 2024 21:58:29 +0800 Subject: [PATCH 1/5] Use GetValueOrDefault instead of TryGetValue to get value from cache dictionary. --- Source/Euonia.Core/Reflection/PropertyAccessorCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Euonia.Core/Reflection/PropertyAccessorCache.cs b/Source/Euonia.Core/Reflection/PropertyAccessorCache.cs index e4155be..34314e4 100644 --- a/Source/Euonia.Core/Reflection/PropertyAccessorCache.cs +++ b/Source/Euonia.Core/Reflection/PropertyAccessorCache.cs @@ -25,12 +25,12 @@ static PropertyAccessorCache() } /// - /// + /// Get the lambda expression for the property. /// /// /// public static LambdaExpression Get(string propertyName) { - return _cache.TryGetValue(propertyName, out var result) ? result : null; + return _cache.GetValueOrDefault(propertyName); } } From 9425f1bdf940d5bd72ad7a30ef06e134b3ac0b4d Mon Sep 17 00:00:00 2001 From: damon Date: Sat, 21 Sep 2024 22:41:18 +0800 Subject: [PATCH 2/5] Use 'Key' instead of 'Name' in InjectAttribute. --- .../Euonia.Modularity/Dependency/InjectAttribute.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Euonia.Modularity/Dependency/InjectAttribute.cs b/Source/Euonia.Modularity/Dependency/InjectAttribute.cs index 9febefa..2f55e0c 100644 --- a/Source/Euonia.Modularity/Dependency/InjectAttribute.cs +++ b/Source/Euonia.Modularity/Dependency/InjectAttribute.cs @@ -4,7 +4,7 @@ namespace System; /// -/// Indicate that the property or parameter would resolved from service container. +/// Indicate that the property or parameter would resolve from service container. /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] public class InjectAttribute : Attribute @@ -12,14 +12,14 @@ public class InjectAttribute : Attribute /// /// Initialize a new instance of . /// - /// - public InjectAttribute(string name = null) + /// An object that specifies the key of service object to get. + public InjectAttribute(object key = null) { - Name = name; + Key = key; } /// - /// Specified the service name or service type name. + /// An object that specifies the key of service object to get. /// - public string Name { get; } + public object Key { get; } } \ No newline at end of file From 8aad15f67246841540fec018fc15167c750f2f9c Mon Sep 17 00:00:00 2001 From: damon Date: Sat, 21 Sep 2024 22:41:57 +0800 Subject: [PATCH 3/5] Bump nuget package to latest version. --- Directory.Packages.props | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1dd649e..a9553b1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,8 +10,8 @@ - - + + @@ -41,7 +41,7 @@ - + @@ -112,7 +112,7 @@ - + @@ -138,6 +138,6 @@ - + \ No newline at end of file From 1dce34b3457ad1146546d4e9043a8d42b8b76261 Mon Sep 17 00:00:00 2001 From: damon Date: Sun, 22 Sep 2024 22:55:07 +0800 Subject: [PATCH 4/5] Rename 'Key' to 'ServiceKey'. --- Source/Euonia.Modularity/Dependency/InjectAttribute.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Euonia.Modularity/Dependency/InjectAttribute.cs b/Source/Euonia.Modularity/Dependency/InjectAttribute.cs index 2f55e0c..3844de5 100644 --- a/Source/Euonia.Modularity/Dependency/InjectAttribute.cs +++ b/Source/Euonia.Modularity/Dependency/InjectAttribute.cs @@ -12,14 +12,14 @@ public class InjectAttribute : Attribute /// /// Initialize a new instance of . /// - /// An object that specifies the key of service object to get. - public InjectAttribute(object key = null) + /// An object that specifies the key of service object to get. + public InjectAttribute(object serviceKey = null) { - Key = key; + ServiceKey = serviceKey; } /// /// An object that specifies the key of service object to get. /// - public object Key { get; } + public object ServiceKey { get; } } \ No newline at end of file From e7a3cd116b186a2c2d2c4396e19578e92be38ffd Mon Sep 17 00:00:00 2001 From: damon Date: Sun, 22 Sep 2024 22:57:48 +0800 Subject: [PATCH 5/5] Support KeyedService auto-injection in business object factory. --- .../Factory/BusinessObjectFactory.cs | 49 ++++--- .../Factory/ObjectReflector.cs | 138 +++++++++--------- 2 files changed, 90 insertions(+), 97 deletions(-) diff --git a/Source/Euonia.Business/Factory/BusinessObjectFactory.cs b/Source/Euonia.Business/Factory/BusinessObjectFactory.cs index 8fd09b8..9029afc 100644 --- a/Source/Euonia.Business/Factory/BusinessObjectFactory.cs +++ b/Source/Euonia.Business/Factory/BusinessObjectFactory.cs @@ -34,13 +34,14 @@ public BusinessObjectFactory(IServiceProvider provider, IObjectActivator activat /// public async Task CreateAsync(params object[] criteria) { - criteria ??= new object[] { null }; + criteria ??= [null]; var method = ObjectReflector.FindFactoryMethod(criteria); var target = GetObjectInstance(); if (target is IEditableObject editable) { editable.MarkAsInsert(); } + try { _activator?.InitializeInstance(target); @@ -56,7 +57,7 @@ public async Task CreateAsync(params object[] criteria) /// public async Task FetchAsync(params object[] criteria) { - criteria ??= new object[] { null }; + criteria ??= [null]; var method = ObjectReflector.FindFactoryMethod(criteria); var target = GetObjectInstance(); try @@ -74,7 +75,7 @@ public async Task FetchAsync(params object[] criteria) /// public async Task InsertAsync(params object[] criteria) { - criteria ??= new object[] { null }; + criteria ??= [null]; var method = ObjectReflector.FindFactoryMethod(criteria); var target = GetObjectInstance(); try @@ -92,7 +93,7 @@ public async Task InsertAsync(params object[] criteria) /// public async Task UpdateAsync(params object[] criteria) { - criteria ??= new object[] { null }; + criteria ??= [null]; var method = ObjectReflector.FindFactoryMethod(criteria); var target = GetObjectInstance(); try @@ -114,18 +115,18 @@ public async Task SaveAsync(TTarget target, CancellationToken { IEditableObject editableObject => editableObject.State switch { - ObjectEditState.Insert => ObjectReflector.FindFactoryMethod(new object[] { cancellationToken }), - ObjectEditState.Update => ObjectReflector.FindFactoryMethod(new object[] { cancellationToken }), - ObjectEditState.Delete => ObjectReflector.FindFactoryMethod(new object[] { cancellationToken }), + ObjectEditState.Insert => ObjectReflector.FindFactoryMethod([cancellationToken]), + ObjectEditState.Update => ObjectReflector.FindFactoryMethod([cancellationToken]), + ObjectEditState.Delete => ObjectReflector.FindFactoryMethod([cancellationToken]), ObjectEditState.None => throw new InvalidOperationException(), _ => throw new ArgumentOutOfRangeException(nameof(target), Resources.IDS_INVALID_STATE) }, - ICommandObject => ObjectReflector.FindFactoryMethod(new object[] { cancellationToken }), + ICommandObject => ObjectReflector.FindFactoryMethod([cancellationToken]), IReadOnlyObject => throw new InvalidOperationException("The operation can not apply for ReadOnlyObject."), - _ => ObjectReflector.FindFactoryMethod(new object[] { cancellationToken }) + _ => ObjectReflector.FindFactoryMethod([cancellationToken]) }; - await InvokeAsync(method, target, new object[] { cancellationToken }); + await InvokeAsync(method, target, [cancellationToken]); return target; } @@ -134,12 +135,12 @@ public async Task SaveAsync(TTarget target, CancellationToken public async Task ExecuteAsync(TTarget target, CancellationToken cancellationToken = default) where TTarget : ICommandObject { - var method = ObjectReflector.FindFactoryMethod(new object[] { cancellationToken }); + var method = ObjectReflector.FindFactoryMethod([cancellationToken]); try { _activator?.InitializeInstance(target); - await InvokeAsync(method, target, new object[] { cancellationToken }); + await InvokeAsync(method, target, [cancellationToken]); return target; } finally @@ -152,7 +153,7 @@ public async Task ExecuteAsync(TTarget target, CancellationTok public async Task ExecuteAsync(params object[] criteria) where TTarget : ICommandObject { - criteria ??= new object[] { null }; + criteria ??= [null]; var method = ObjectReflector.FindFactoryMethod(criteria); var target = GetObjectInstance(); @@ -171,7 +172,7 @@ public async Task ExecuteAsync(params object[] criteria) /// public async Task DeleteAsync(params object[] criteria) { - criteria ??= new object[] { null }; + criteria ??= [null]; var method = ObjectReflector.FindFactoryMethod(criteria); var target = GetObjectInstance(); @@ -208,28 +209,28 @@ private static async Task InvokeAsync(MethodInfo method, TTarget target private TTarget GetObjectInstance() { var @object = ActivatorUtilities.GetServiceOrCreateInstance(_provider); - if (@object is IUseBusinessContext ctx) - { - ctx.BusinessContext = _provider.GetRequiredService(); - } - - if (@object is IHasLazyServiceProvider lazy) + switch (@object) { - lazy.LazyServiceProvider = _provider.GetRequiredService(); + case IUseBusinessContext ctx: + ctx.BusinessContext = _provider.GetRequiredService(); + break; + case IHasLazyServiceProvider lazy: + lazy.LazyServiceProvider = _provider.GetRequiredService(); + 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); } } diff --git a/Source/Euonia.Business/Factory/ObjectReflector.cs b/Source/Euonia.Business/Factory/ObjectReflector.cs index 28af9fd..783800a 100644 --- a/Source/Euonia.Business/Factory/ObjectReflector.cs +++ b/Source/Euonia.Business/Factory/ObjectReflector.cs @@ -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>> _propertyCache = new(); + private static readonly ConcurrentDictionary>> _propertyCache = new(); private static readonly ConcurrentDictionary _factoryMethods = new(); - internal static List> GetAutoInjectProperties(Type objectType) + internal static List> GetAutoInjectProperties(Type objectType) { return _propertyCache.GetOrAdd(objectType, type => { - var autoInjectProperties = new List>(); + var autoInjectProperties = new List>(); var propertiesOfType = type.GetRuntimeProperties().ToList(); foreach (var property in propertiesOfType) { - if (property.GetCustomAttribute() == null) + var attribute = property.GetCustomAttribute(); + + 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; @@ -51,20 +53,6 @@ internal static MethodInfo FindFactoryMethod(object[] crite internal static MethodInfo FindFactoryMethod(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(attributeType, criteria); return _factoryMethods.GetOrAdd(name, () => FindMatchedMethod(attributeType, criteria)); } @@ -82,14 +70,7 @@ private static MethodInfo FindMatchedMethod(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 { @@ -157,7 +138,7 @@ private static MethodInfo FindMatchedMethod(Type attributeType, object[ { var lastParam = method.GetParameters().LastOrDefault(); if (lastParam != null && lastParam.ParameterType == typeof(object[]) && - lastParam.GetCustomAttributes().Any()) + lastParam.GetCustomAttributes().Any()) { matches.Add(Tuple.Create(method, 1 + score)); } @@ -213,7 +194,7 @@ private static MethodInfo FindMatchedMethod(Type attributeType, object[ public static MethodInfo FindMatchedMethod(Type attributeType, IReadOnlyList 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}"); @@ -257,7 +238,7 @@ private static List> GetCandidateMethods(Type targetType, var result = new List>(); 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) @@ -288,60 +269,71 @@ private static List> GetCandidateMethods(Type targetType, /// private static Tuple 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) @@ -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)}",