- allow async onCreate method by introducing async resolve

master
Simon G. 1 year ago
parent 9a5abca32b
commit d79158aaa6
Signed by: SimonG
GPG Key ID: 0B82B964BA536523
  1. 11
      LightweightIocContainer.sln.DotSettings
  2. 11
      LightweightIocContainer/Factories/TypedFactory.cs
  3. 28
      LightweightIocContainer/GenericMethodCaller.cs
  4. 15
      LightweightIocContainer/Interfaces/IIocResolver.cs
  5. 3
      LightweightIocContainer/Interfaces/Registrations/Fluent/IOnCreate.cs
  6. 86
      LightweightIocContainer/IocContainer.cs
  7. 50
      LightweightIocContainer/LightweightIocContainer.xml
  8. 2
      LightweightIocContainer/Registrations/RegistrationBase.cs
  9. 24
      LightweightIocContainer/Registrations/TypedRegistration.cs
  10. 10
      LightweightIocContainer/TypeExtension.cs
  11. 96
      Test.LightweightIocContainer/AsyncFactoryTest.cs
  12. 12
      Test.LightweightIocContainer/OnCreateTest.cs

@ -4,16 +4,21 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_WHILE_STMT/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_WHILE_STMT/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/USE_INDENT_FROM_VS/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/USE_INDENT_FROM_VS/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">200</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">200</s:Int64>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Author: $USER_NAME$&#xD; <s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Author: ${User.Name}&#xD;
Created: $CREATED_YEAR$-$CREATED_MONTH$-$CREATED_DAY$&#xD; Created: ${File.CreatedYear}-${File.CreatedMonth}-${File.CreatedDay}&#xD;
Copyright(c) $CREATED_YEAR$ SimonG. All Rights Reserved.</s:String> Copyright(c) ${File.CreatedYear} SimonG. All Rights Reserved.</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Method/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Method/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=8284009d_002De743_002D4d89_002D9402_002Da5bf9a89b657/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Methods"&gt;&lt;ElementKinds&gt;&lt;Kind Name="METHOD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gockner/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=gockner/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=multiton/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=multiton/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=multitons/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=multitons/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

@ -51,7 +51,7 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
FieldBuilder helperFieldBuilder = typeBuilder.DefineField("_helper", typeof(FactoryHelper), FieldAttributes.Private | FieldAttributes.InitOnly); FieldBuilder helperFieldBuilder = typeBuilder.DefineField("_helper", typeof(FactoryHelper), FieldAttributes.Private | FieldAttributes.InitOnly);
//add ctor //add ctor
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new[] {typeof(IocContainer), typeof(FactoryHelper)}); ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, [typeof(IocContainer), typeof(FactoryHelper)]);
ILGenerator constructorGenerator = constructorBuilder.GetILGenerator(); ILGenerator constructorGenerator = constructorBuilder.GetILGenerator();
constructorGenerator.Emit(OpCodes.Ldarg_0); constructorGenerator.Emit(OpCodes.Ldarg_0);
constructorGenerator.Emit(OpCodes.Ldarg_1); constructorGenerator.Emit(OpCodes.Ldarg_1);
@ -129,13 +129,18 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
generator.EmitCall(OpCodes.Call, emptyArray, null); generator.EmitCall(OpCodes.Call, emptyArray, null);
} }
generator.EmitCall(OpCodes.Call, typeof(FactoryHelper).GetMethod(nameof(FactoryHelper.ConvertPassedNull), new[] { typeof(MethodBase), typeof(object?[]) })!, null); generator.EmitCall(OpCodes.Call, typeof(FactoryHelper).GetMethod(nameof(FactoryHelper.ConvertPassedNull), [typeof(MethodBase), typeof(object?[])])!, null);
generator.Emit(OpCodes.Stloc_1); generator.Emit(OpCodes.Stloc_1);
generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, containerFieldBuilder); generator.Emit(OpCodes.Ldfld, containerFieldBuilder);
generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldloc_1);
generator.EmitCall(OpCodes.Call, typeof(IocContainer).GetMethod(nameof(IocContainer.FactoryResolve), new[] { typeof(object?[]) })!.MakeGenericMethod(createMethod.ReturnType), null); Type? asyncReturnType = createMethod.ReturnType.GetAsyncReturnType();
if (asyncReturnType is not null)
generator.EmitCall(OpCodes.Call, typeof(IocContainer).GetMethod(nameof(IocContainer.FactoryResolveAsync), [typeof(object?[])])!.MakeGenericMethod(asyncReturnType), null);
else
generator.EmitCall(OpCodes.Call, typeof(IocContainer).GetMethod(nameof(IocContainer.FactoryResolve), [typeof(object?[])])!.MakeGenericMethod(createMethod.ReturnType), null);
generator.Emit(OpCodes.Castclass, createMethod.ReturnType); generator.Emit(OpCodes.Castclass, createMethod.ReturnType);
generator.Emit(OpCodes.Ret); generator.Emit(OpCodes.Ret);
} }

@ -41,6 +41,31 @@ internal static class GenericMethodCaller
} }
} }
public static async Task<object?> CallAsync(object caller, string functionName, Type genericParameter, BindingFlags bindingFlags, params object?[] parameters)
{
MethodInfo? method = caller.GetType().GetMethod(functionName, bindingFlags);
MethodInfo? genericMethod = method?.MakeGenericMethod(genericParameter);
if (genericMethod == null)
throw new GenericMethodNotFoundException(functionName);
try //exceptions thrown by methods called with invoke are wrapped into another exception, the exception thrown by the invoked method can be returned by `Exception.GetBaseException()`
{
object? result = genericMethod.Invoke(caller, parameters);
if (result is null)
return null;
if (result is Task<object?> task)
return await task;
return result;
}
catch (Exception ex)
{
throw ex.GetBaseException();
}
}
/// <summary> /// <summary>
/// Call a private generic method without generic type parameters /// Call a private generic method without generic type parameters
/// </summary> /// </summary>
@ -53,4 +78,7 @@ internal static class GenericMethodCaller
/// <exception cref="Exception">Any <see cref="Exception"/> thrown after invoking the generic method</exception> /// <exception cref="Exception">Any <see cref="Exception"/> thrown after invoking the generic method</exception>
public static object? CallPrivate(object caller, string functionName, Type genericParameter, params object?[] parameters) => public static object? CallPrivate(object caller, string functionName, Type genericParameter, params object?[] parameters) =>
Call(caller, functionName, genericParameter, BindingFlags.NonPublic | BindingFlags.Instance, parameters); Call(caller, functionName, genericParameter, BindingFlags.NonPublic | BindingFlags.Instance, parameters);
public static async Task<object?> CallPrivateAsync(object caller, string functionName, Type genericParameter, params object?[] parameters) =>
await CallAsync(caller, functionName, genericParameter, BindingFlags.NonPublic | BindingFlags.Instance, parameters);
} }

@ -16,6 +16,13 @@ public interface IIocResolver : IDisposable
/// <returns>An instance of the given <see cref="Type"/></returns> /// <returns>An instance of the given <see cref="Type"/></returns>
T Resolve<T>(); T Resolve<T>();
/// <summary>
/// Gets an instance of the given <see cref="Type"/>
/// </summary>
/// <typeparam name="T">The given <see cref="Type"/></typeparam>
/// <returns>An instance of the given <see cref="Type"/></returns>
Task<T> ResolveAsync<T>();
/// <summary> /// <summary>
/// Gets an instance of the given <see cref="Type"/> /// Gets an instance of the given <see cref="Type"/>
/// </summary> /// </summary>
@ -23,4 +30,12 @@ public interface IIocResolver : IDisposable
/// <param name="arguments">The constructor arguments</param> /// <param name="arguments">The constructor arguments</param>
/// <returns>An instance of the given <see cref="Type"/></returns> /// <returns>An instance of the given <see cref="Type"/></returns>
T Resolve<T>(params object[] arguments); T Resolve<T>(params object[] arguments);
/// <summary>
/// Gets an instance of the given <see cref="Type"/>
/// </summary>
/// <typeparam name="T">The given <see cref="Type"/></typeparam>
/// <param name="arguments">The constructor arguments</param>
/// <returns>An instance of the given <see cref="Type"/></returns>
Task<T> ResolveAsync<T>(params object[] arguments);
} }

@ -16,6 +16,7 @@ public interface IOnCreate
/// <para>Can be set in the <see cref="IIocInstaller"/> by calling <see cref="IOnCreate{TInterface, TImplementation}.OnCreate"/></para> /// <para>Can be set in the <see cref="IIocInstaller"/> by calling <see cref="IOnCreate{TInterface, TImplementation}.OnCreate"/></para>
/// </summary> /// </summary>
internal Action<object?>? OnCreateAction { get; } internal Action<object?>? OnCreateAction { get; }
internal Func<object?, Task>? OnCreateActionAsync { get; }
} }
/// <summary> /// <summary>
@ -31,4 +32,6 @@ public interface IOnCreate<TInterface, TImplementation> : IOnCreate where TImple
/// <param name="action">The <see cref="Action{T}"/></param> /// <param name="action">The <see cref="Action{T}"/></param>
/// <returns>The current instance of this <see cref="ITypedRegistration{TInterface,TImplementation}"/></returns> /// <returns>The current instance of this <see cref="ITypedRegistration{TInterface,TImplementation}"/></returns>
ITypedRegistration<TInterface, TImplementation> OnCreate(Action<TImplementation?> action); ITypedRegistration<TInterface, TImplementation> OnCreate(Action<TImplementation?> action);
ITypedRegistration<TInterface, TImplementation> OnCreateAsync(Func<TImplementation?, Task> action);
} }

@ -104,6 +104,13 @@ public class IocContainer : IIocContainer, IIocResolver
/// <returns>An instance of the given <see cref="Type"/></returns> /// <returns>An instance of the given <see cref="Type"/></returns>
public virtual T Resolve<T>() => ResolveInternal<T>(null); public virtual T Resolve<T>() => ResolveInternal<T>(null);
/// <summary>
/// Gets an instance of the given <see cref="Type"/>
/// </summary>
/// <typeparam name="T">The given <see cref="Type"/></typeparam>
/// <returns>An instance of the given <see cref="Type"/></returns>
public Task<T> ResolveAsync<T>() => ResolveInternalAsync<T>(null);
/// <summary> /// <summary>
/// Gets an instance of the given <see cref="Type"/> /// Gets an instance of the given <see cref="Type"/>
/// </summary> /// </summary>
@ -112,6 +119,14 @@ public class IocContainer : IIocContainer, IIocResolver
/// <returns>An instance of the given <see cref="Type"/></returns> /// <returns>An instance of the given <see cref="Type"/></returns>
public T Resolve<T>(params object[] arguments) => ResolveInternal<T>(arguments); public T Resolve<T>(params object[] arguments) => ResolveInternal<T>(arguments);
/// <summary>
/// Gets an instance of the given <see cref="Type"/>
/// </summary>
/// <typeparam name="T">The given <see cref="Type"/></typeparam>
/// <param name="arguments">The constructor arguments</param>
/// <returns>An instance of the given <see cref="Type"/></returns>
public Task<T> ResolveAsync<T>(params object[] arguments) => ResolveInstanceAsync<T>(arguments);
/// <summary> /// <summary>
/// Gets an instance of the given <see cref="Type"/> for a factory /// Gets an instance of the given <see cref="Type"/> for a factory
/// </summary> /// </summary>
@ -120,6 +135,14 @@ public class IocContainer : IIocContainer, IIocResolver
/// <returns>An instance of the given <see cref="Type"/></returns> /// <returns>An instance of the given <see cref="Type"/></returns>
public T FactoryResolve<T>(params object?[] arguments) => ResolveInternal<T>(arguments, null, true); public T FactoryResolve<T>(params object?[] arguments) => ResolveInternal<T>(arguments, null, true);
/// <summary>
/// Gets an instance of the given <see cref="Type"/> for a factory
/// </summary>
/// <typeparam name="T">The given <see cref="Type"/></typeparam>
/// <param name="arguments">The constructor arguments</param>
/// <returns>An instance of the given <see cref="Type"/></returns>
public Task<T> FactoryResolveAsync<T>(params object?[] arguments) => ResolveInternalAsync<T>(arguments, null, true);
/// <summary> /// <summary>
/// Gets an instance of a given registered <see cref="Type"/> /// Gets an instance of a given registered <see cref="Type"/>
/// </summary> /// </summary>
@ -140,6 +163,18 @@ public class IocContainer : IIocContainer, IIocResolver
throw new Exception("Resolve Error"); throw new Exception("Resolve Error");
} }
private async Task<T> ResolveInternalAsync<T>(object?[]? arguments, List<Type>? resolveStack = null, bool isFactoryResolve = false)
{
(bool success, object resolvedObject, Exception? exception) = TryResolve<T>(arguments, resolveStack, isFactoryResolve);
if (success)
return await ResolveInstanceAsync<T>(resolvedObject);
if (exception is not null)
throw exception;
throw new Exception("Resolve Error");
}
/// <summary> /// <summary>
/// Tries to resolve the given <see cref="Type"/> with the given arguments /// Tries to resolve the given <see cref="Type"/> with the given arguments
/// </summary> /// </summary>
@ -254,7 +289,7 @@ public class IocContainer : IIocContainer, IIocResolver
if (toBeResolvedPlaceholder.Parameters == null) if (toBeResolvedPlaceholder.Parameters == null)
return CreateInstance<T>(toBeResolvedPlaceholder.ResolvedRegistration, null); return CreateInstance<T>(toBeResolvedPlaceholder.ResolvedRegistration, null);
List<object?> parameters = new(); List<object?> parameters = [];
foreach (object? parameter in toBeResolvedPlaceholder.Parameters) foreach (object? parameter in toBeResolvedPlaceholder.Parameters)
{ {
if (parameter != null) if (parameter != null)
@ -271,6 +306,32 @@ public class IocContainer : IIocContainer, IIocResolver
return CreateInstance<T>(toBeResolvedPlaceholder.ResolvedRegistration, parameters.ToArray()); return CreateInstance<T>(toBeResolvedPlaceholder.ResolvedRegistration, parameters.ToArray());
} }
private async Task<T> ResolvePlaceholderAsync<T>(InternalToBeResolvedPlaceholder toBeResolvedPlaceholder)
{
object? existingInstance = TryGetExistingInstance<T>(toBeResolvedPlaceholder.ResolvedRegistration, toBeResolvedPlaceholder.Parameters);
if (existingInstance is T instance)
return instance;
if (toBeResolvedPlaceholder.Parameters == null)
return await CreateInstanceAsync<T>(toBeResolvedPlaceholder.ResolvedRegistration, null);
List<object?> parameters = [];
foreach (object? parameter in toBeResolvedPlaceholder.Parameters)
{
if (parameter != null)
{
Type type = parameter is IInternalToBeResolvedPlaceholder internalToBeResolvedPlaceholder ?
internalToBeResolvedPlaceholder.ResolvedType : parameter.GetType();
parameters.Add(await ResolveInstanceNonGenericAsync(type, parameter));
}
else
parameters.Add(parameter);
}
return await CreateInstanceAsync<T>(toBeResolvedPlaceholder.ResolvedRegistration, parameters.ToArray());
}
/// <summary> /// <summary>
/// Resolve the given object instance /// Resolve the given object instance
/// </summary> /// </summary>
@ -287,6 +348,15 @@ public class IocContainer : IIocContainer, IIocResolver
_ => throw new InternalResolveException("Resolve returned wrong type.") _ => throw new InternalResolveException("Resolve returned wrong type.")
}; };
private async Task<T> ResolveInstanceAsync<T>(object resolvedObject) =>
resolvedObject switch
{
T instance => instance,
InternalToBeResolvedPlaceholder toBeResolvedPlaceholder => await ResolvePlaceholderAsync<T>(toBeResolvedPlaceholder),
InternalFactoryMethodPlaceholder<T> factoryMethodPlaceholder => await CreateInstanceAsync<T>(factoryMethodPlaceholder.SingleTypeRegistration, null),
_ => throw new InternalResolveException("Resolve returned wrong type.")
};
/// <summary> /// <summary>
/// Resolve the given object instance without generic arguments /// Resolve the given object instance without generic arguments
/// </summary> /// </summary>
@ -297,6 +367,9 @@ public class IocContainer : IIocContainer, IIocResolver
private object? ResolveInstanceNonGeneric(Type type, object resolvedObject) => private object? ResolveInstanceNonGeneric(Type type, object resolvedObject) =>
GenericMethodCaller.CallPrivate(this, nameof(ResolveInstance), type, resolvedObject); GenericMethodCaller.CallPrivate(this, nameof(ResolveInstance), type, resolvedObject);
private Task<object?> ResolveInstanceNonGenericAsync(Type type, object resolvedObject) =>
GenericMethodCaller.CallPrivateAsync(this, nameof(ResolveInstance), type, resolvedObject);
/// <summary> /// <summary>
/// Creates an instance of a given <see cref="Type"/> /// Creates an instance of a given <see cref="Type"/>
/// </summary> /// </summary>
@ -326,7 +399,16 @@ public class IocContainer : IIocContainer, IIocResolver
_singletons.Add((GetType<T>(registration), instance)); _singletons.Add((GetType<T>(registration), instance));
if (registration is IOnCreate onCreateRegistration) if (registration is IOnCreate onCreateRegistration)
onCreateRegistration.OnCreateAction?.Invoke(instance); //TODO: Allow async OnCreateAction? onCreateRegistration.OnCreateAction?.Invoke(instance);
return instance;
}
private async Task<T> CreateInstanceAsync<T>(IRegistration registration, object?[]? arguments)
{
T instance = CreateInstance<T>(registration, arguments);
if (registration is IOnCreate { OnCreateActionAsync: not null } onCreateRegistration)
await onCreateRegistration.OnCreateActionAsync.Invoke(instance);
return instance; return instance;
} }

@ -551,6 +551,13 @@
<typeparam name="T">The given <see cref="T:System.Type"/></typeparam> <typeparam name="T">The given <see cref="T:System.Type"/></typeparam>
<returns>An instance of the given <see cref="T:System.Type"/></returns> <returns>An instance of the given <see cref="T:System.Type"/></returns>
</member> </member>
<member name="M:LightweightIocContainer.Interfaces.IIocResolver.ResolveAsync``1">
<summary>
Gets an instance of the given <see cref="T:System.Type"/>
</summary>
<typeparam name="T">The given <see cref="T:System.Type"/></typeparam>
<returns>An instance of the given <see cref="T:System.Type"/></returns>
</member>
<member name="M:LightweightIocContainer.Interfaces.IIocResolver.Resolve``1(System.Object[])"> <member name="M:LightweightIocContainer.Interfaces.IIocResolver.Resolve``1(System.Object[])">
<summary> <summary>
Gets an instance of the given <see cref="T:System.Type"/> Gets an instance of the given <see cref="T:System.Type"/>
@ -559,6 +566,14 @@
<param name="arguments">The constructor arguments</param> <param name="arguments">The constructor arguments</param>
<returns>An instance of the given <see cref="T:System.Type"/></returns> <returns>An instance of the given <see cref="T:System.Type"/></returns>
</member> </member>
<member name="M:LightweightIocContainer.Interfaces.IIocResolver.ResolveAsync``1(System.Object[])">
<summary>
Gets an instance of the given <see cref="T:System.Type"/>
</summary>
<typeparam name="T">The given <see cref="T:System.Type"/></typeparam>
<param name="arguments">The constructor arguments</param>
<returns>An instance of the given <see cref="T:System.Type"/></returns>
</member>
<member name="T:LightweightIocContainer.Interfaces.Installers.IAssemblyInstaller"> <member name="T:LightweightIocContainer.Interfaces.Installers.IAssemblyInstaller">
<summary> <summary>
An <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> that installs all <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/>s for its given <see cref="T:System.Reflection.Assembly"/> An <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> that installs all <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/>s for its given <see cref="T:System.Reflection.Assembly"/>
@ -975,6 +990,13 @@
<typeparam name="T">The given <see cref="T:System.Type"/></typeparam> <typeparam name="T">The given <see cref="T:System.Type"/></typeparam>
<returns>An instance of the given <see cref="T:System.Type"/></returns> <returns>An instance of the given <see cref="T:System.Type"/></returns>
</member> </member>
<member name="M:LightweightIocContainer.IocContainer.ResolveAsync``1">
<summary>
Gets an instance of the given <see cref="T:System.Type"/>
</summary>
<typeparam name="T">The given <see cref="T:System.Type"/></typeparam>
<returns>An instance of the given <see cref="T:System.Type"/></returns>
</member>
<member name="M:LightweightIocContainer.IocContainer.Resolve``1(System.Object[])"> <member name="M:LightweightIocContainer.IocContainer.Resolve``1(System.Object[])">
<summary> <summary>
Gets an instance of the given <see cref="T:System.Type"/> Gets an instance of the given <see cref="T:System.Type"/>
@ -983,6 +1005,14 @@
<param name="arguments">The constructor arguments</param> <param name="arguments">The constructor arguments</param>
<returns>An instance of the given <see cref="T:System.Type"/></returns> <returns>An instance of the given <see cref="T:System.Type"/></returns>
</member> </member>
<member name="M:LightweightIocContainer.IocContainer.ResolveAsync``1(System.Object[])">
<summary>
Gets an instance of the given <see cref="T:System.Type"/>
</summary>
<typeparam name="T">The given <see cref="T:System.Type"/></typeparam>
<param name="arguments">The constructor arguments</param>
<returns>An instance of the given <see cref="T:System.Type"/></returns>
</member>
<member name="M:LightweightIocContainer.IocContainer.FactoryResolve``1(System.Object[])"> <member name="M:LightweightIocContainer.IocContainer.FactoryResolve``1(System.Object[])">
<summary> <summary>
Gets an instance of the given <see cref="T:System.Type"/> for a factory Gets an instance of the given <see cref="T:System.Type"/> for a factory
@ -991,6 +1021,14 @@
<param name="arguments">The constructor arguments</param> <param name="arguments">The constructor arguments</param>
<returns>An instance of the given <see cref="T:System.Type"/></returns> <returns>An instance of the given <see cref="T:System.Type"/></returns>
</member> </member>
<member name="M:LightweightIocContainer.IocContainer.FactoryResolveAsync``1(System.Object[])">
<summary>
Gets an instance of the given <see cref="T:System.Type"/> for a factory
</summary>
<typeparam name="T">The given <see cref="T:System.Type"/></typeparam>
<param name="arguments">The constructor arguments</param>
<returns>An instance of the given <see cref="T:System.Type"/></returns>
</member>
<member name="M:LightweightIocContainer.IocContainer.ResolveInternal``1(System.Object[],System.Collections.Generic.List{System.Type},System.Boolean)"> <member name="M:LightweightIocContainer.IocContainer.ResolveInternal``1(System.Object[],System.Collections.Generic.List{System.Type},System.Boolean)">
<summary> <summary>
Gets an instance of a given registered <see cref="T:System.Type"/> Gets an instance of a given registered <see cref="T:System.Type"/>
@ -1856,12 +1894,24 @@
<para>Can be set in the <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> by calling <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IOnCreate`2.OnCreate(System.Action{`1})"/></para> <para>Can be set in the <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> by calling <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IOnCreate`2.OnCreate(System.Action{`1})"/></para>
</summary> </summary>
</member> </member>
<member name="P:LightweightIocContainer.Registrations.TypedRegistration`2.OnCreateActionAsync">
<summary>
This <see cref="T:System.Action"/> is invoked when an instance of this type is created.
<para>Can be set in the <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> by calling <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IOnCreate`2.OnCreate(System.Action{`1})"/></para>
</summary>
</member>
<member name="P:LightweightIocContainer.Registrations.TypedRegistration`2.LightweightIocContainer#Interfaces#Registrations#Fluent#IOnCreate#OnCreateAction"> <member name="P:LightweightIocContainer.Registrations.TypedRegistration`2.LightweightIocContainer#Interfaces#Registrations#Fluent#IOnCreate#OnCreateAction">
<summary> <summary>
This <see cref="T:System.Action"/> is invoked when an instance of this type is created. This <see cref="T:System.Action"/> is invoked when an instance of this type is created.
<para>Can be set in the <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> by calling <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IOnCreate`2.OnCreate(System.Action{`1})"/></para> <para>Can be set in the <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> by calling <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IOnCreate`2.OnCreate(System.Action{`1})"/></para>
</summary> </summary>
</member> </member>
<member name="P:LightweightIocContainer.Registrations.TypedRegistration`2.LightweightIocContainer#Interfaces#Registrations#Fluent#IOnCreate#OnCreateActionAsync">
<summary>
This <see cref="T:System.Action"/> is invoked when an instance of this type is created.
<para>Can be set in the <see cref="T:LightweightIocContainer.Interfaces.Installers.IIocInstaller"/> by calling <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IOnCreate`2.OnCreate(System.Action{`1})"/></para>
</summary>
</member>
<member name="M:LightweightIocContainer.Registrations.TypedRegistration`2.OnCreate(System.Action{`1})"> <member name="M:LightweightIocContainer.Registrations.TypedRegistration`2.OnCreate(System.Action{`1})">
<summary> <summary>
Pass an <see cref="T:System.Action`1"/> that will be invoked when an instance of this type is created Pass an <see cref="T:System.Action`1"/> that will be invoked when an instance of this type is created

@ -176,7 +176,7 @@ internal abstract class RegistrationBase : IRegistrationBase, IWithFactoryIntern
if (Factory == null) if (Factory == null)
return; return;
if (Factory.CreateMethods.All(c => c.ReturnType != InterfaceType)) if (Factory.CreateMethods.All(c => c.ReturnType != InterfaceType) && Factory.CreateMethods.All(c => c.ReturnType.GetAsyncReturnType() != InterfaceType))
throw new InvalidFactoryRegistrationException($"No create method that can create {InterfaceType}."); throw new InvalidFactoryRegistrationException($"No create method that can create {InterfaceType}.");
} }

@ -36,12 +36,24 @@ internal class TypedRegistration<TInterface, TImplementation> : RegistrationBase
/// </summary> /// </summary>
private Action<object?>? OnCreateAction { get; set; } private Action<object?>? OnCreateAction { get; set; }
/// <summary>
/// This <see cref="Action"/> is invoked when an instance of this type is created.
/// <para>Can be set in the <see cref="IIocInstaller"/> by calling <see cref="IOnCreate{TInterface,TImplementation}.OnCreate"/></para>
/// </summary>
private Func<object?, Task>? OnCreateActionAsync { get; set; }
/// <summary> /// <summary>
/// This <see cref="Action"/> is invoked when an instance of this type is created. /// This <see cref="Action"/> is invoked when an instance of this type is created.
/// <para>Can be set in the <see cref="IIocInstaller"/> by calling <see cref="IOnCreate{TInterface,TImplementation}.OnCreate"/></para> /// <para>Can be set in the <see cref="IIocInstaller"/> by calling <see cref="IOnCreate{TInterface,TImplementation}.OnCreate"/></para>
/// </summary> /// </summary>
Action<object?>? IOnCreate.OnCreateAction => OnCreateAction; Action<object?>? IOnCreate.OnCreateAction => OnCreateAction;
/// <summary>
/// This <see cref="Action"/> is invoked when an instance of this type is created.
/// <para>Can be set in the <see cref="IIocInstaller"/> by calling <see cref="IOnCreate{TInterface,TImplementation}.OnCreate"/></para>
/// </summary>
Func<object?, Task>? IOnCreate.OnCreateActionAsync => OnCreateActionAsync;
/// <summary> /// <summary>
/// Pass an <see cref="Action{T}"/> that will be invoked when an instance of this type is created /// Pass an <see cref="Action{T}"/> that will be invoked when an instance of this type is created
/// </summary> /// </summary>
@ -53,6 +65,12 @@ internal class TypedRegistration<TInterface, TImplementation> : RegistrationBase
return this; return this;
} }
public ITypedRegistration<TInterface, TImplementation> OnCreateAsync(Func<TImplementation?, Task> action)
{
OnCreateActionAsync = a => action((TImplementation?) a);
return this;
}
/// <summary> /// <summary>
/// Validate the <see cref="DisposeStrategy"/> for the <see cref="ImplementationType"/> and <see cref="Lifestyle"/> /// Validate the <see cref="DisposeStrategy"/> for the <see cref="ImplementationType"/> and <see cref="Lifestyle"/>
/// </summary> /// </summary>
@ -72,6 +90,12 @@ internal class TypedRegistration<TInterface, TImplementation> : RegistrationBase
if (OnCreateAction != null && typedRegistration.OnCreateAction == null) if (OnCreateAction != null && typedRegistration.OnCreateAction == null)
return false; return false;
if (OnCreateActionAsync == null && typedRegistration.OnCreateActionAsync != null)
return false;
if (OnCreateActionAsync != null && typedRegistration.OnCreateActionAsync == null)
return false;
return ImplementationType == typedRegistration.ImplementationType; return ImplementationType == typedRegistration.ImplementationType;
} }

@ -12,4 +12,14 @@ internal static class TypeExtension
/// <param name="type">The given <see cref="Type"/></param> /// <param name="type">The given <see cref="Type"/></param>
/// <returns>The default value for the given <see cref="Type"/></returns> /// <returns>The default value for the given <see cref="Type"/></returns>
public static object? GetDefault(this Type type) => type.IsValueType ? Activator.CreateInstance(type) : null; public static object? GetDefault(this Type type) => type.IsValueType ? Activator.CreateInstance(type) : null;
public static Type? GetAsyncReturnType(this Type type)
{
if (!type.IsGenericType)
return null;
if (type.GetGenericTypeDefinition() != typeof(Task<>))
return null;
return type.GenericTypeArguments[0];
}
} }

@ -0,0 +1,96 @@
// Author: simon.gockner
// Created: 2024-11-25
// Copyright(c) 2024 SimonG. All Rights Reserved.
using LightweightIocContainer;
using NUnit.Framework;
namespace Test.LightweightIocContainer;
[TestFixture]
public class AsyncFactoryTest
{
public interface ITest
{
bool IsInitialized { get; }
Task Initialize();
}
public class Test : ITest
{
public bool IsInitialized { get; private set; }
public virtual async Task Initialize()
{
await Task.Delay(200);
IsInitialized = true;
}
}
public class MultitonTest(int id) : Test
{
public int Id { get; } = id;
public override async Task Initialize()
{
if (IsInitialized)
throw new Exception();
await base.Initialize();
}
}
public interface ITestFactory
{
Task<ITest> Create();
}
public interface IMultitonTestFactory
{
Task<ITest> Create(int id);
}
[Test]
public async Task TestAsyncFactoryResolve()
{
IocContainer container = new();
container.Register(r => r.Add<ITest, Test>().WithFactory<ITestFactory>());
ITestFactory testFactory = container.Resolve<ITestFactory>();
ITest test = await testFactory.Create();
Assert.IsInstanceOf<Test>(test);
}
[Test]
public async Task TestAsyncFactoryResolveOnCreateCalled()
{
IocContainer container = new();
container.Register(r => r.Add<ITest, Test>().OnCreateAsync(t => t.Initialize()).WithFactory<ITestFactory>());
ITestFactory testFactory = container.Resolve<ITestFactory>();
ITest test = await testFactory.Create();
Assert.IsInstanceOf<Test>(test);
Assert.That(test.IsInitialized, Is.True);
}
[Test]
public async Task TestAsyncMultitonFactoryResolveOnCreateCalledCorrectly()
{
IocContainer container = new();
container.Register(r => r.AddMultiton<ITest, MultitonTest, int>().OnCreateAsync(t => t.Initialize()).WithFactory<IMultitonTestFactory>());
IMultitonTestFactory testFactory = container.Resolve<IMultitonTestFactory>();
ITest test1 = await testFactory.Create(1);
ITest test2 = await testFactory.Create(2);
ITest anotherTest1 = await testFactory.Create(1);
Assert.IsInstanceOf<Test>(test1);
Assert.That(test1.IsInitialized, Is.True);
Assert.IsInstanceOf<Test>(test2);
Assert.That(test2.IsInitialized, Is.True);
Assert.AreSame(test1, anotherTest1);
}
}

@ -21,6 +21,7 @@ public class OnCreateTest
private class Test : ITest private class Test : ITest
{ {
public void DoSomething() => throw new Exception(); public void DoSomething() => throw new Exception();
public Task InitializeAsync() => throw new Exception();
} }
@ -34,4 +35,15 @@ public class OnCreateTest
Assert.Throws<Exception>(() => testRegistration.OnCreateAction!(test)); Assert.Throws<Exception>(() => testRegistration.OnCreateAction!(test));
} }
[Test]
public void TestOnCreateAsync()
{
RegistrationFactory registrationFactory = new(Substitute.For<IocContainer>());
ITypedRegistration<ITest, Test> testRegistration = registrationFactory.Register<ITest, Test>(Lifestyle.Transient).OnCreateAsync(t => t.InitializeAsync());
Test test = new();
Assert.Throws<Exception>(() => testRegistration.OnCreateActionAsync!(test));
}
} }
Loading…
Cancel
Save