diff --git a/DynamicInjector-Test/DynamicInjector-Test.csproj b/DynamicInjector-Test/DynamicInjector-Test.csproj new file mode 100644 index 0000000..bc47bef --- /dev/null +++ b/DynamicInjector-Test/DynamicInjector-Test.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + DynamicInjector_Test + enable + enable + + + + + + + diff --git a/DynamicInjector-Test/FirstTestService.cs b/DynamicInjector-Test/FirstTestService.cs new file mode 100644 index 0000000..4edc5a4 --- /dev/null +++ b/DynamicInjector-Test/FirstTestService.cs @@ -0,0 +1,11 @@ +namespace DynamicInjector_Test; + +internal class FirstTestService : IFirstTestService +{ + private int count = 0; + + public void Print() + { + Console.WriteLine(nameof(FirstTestService) + " | " + count++); + } +} \ No newline at end of file diff --git a/DynamicInjector-Test/IFirstTestService.cs b/DynamicInjector-Test/IFirstTestService.cs new file mode 100644 index 0000000..11181bd --- /dev/null +++ b/DynamicInjector-Test/IFirstTestService.cs @@ -0,0 +1,6 @@ +namespace DynamicInjector_Test; + +internal interface IFirstTestService +{ + public void Print(); +} \ No newline at end of file diff --git a/DynamicInjector-Test/Program.cs b/DynamicInjector-Test/Program.cs new file mode 100644 index 0000000..8bfa88e --- /dev/null +++ b/DynamicInjector-Test/Program.cs @@ -0,0 +1,19 @@ +using DynamicInjector; + +namespace DynamicInjector_Test; + +internal class Prgramm +{ + private static void Main() + { + InjectionContainer injector = new InjectionContainer(); + injector.RegisterGlobal(); + injector.RegisterLocal(); + + TestClass? testClass = injector.Resolve(); + + testClass.Test(); + + injector.Invoke(testClass, (c) => c.Test); + } +} \ No newline at end of file diff --git a/DynamicInjector-Test/SecondTestService.cs b/DynamicInjector-Test/SecondTestService.cs new file mode 100644 index 0000000..4068db2 --- /dev/null +++ b/DynamicInjector-Test/SecondTestService.cs @@ -0,0 +1,18 @@ +namespace DynamicInjector_Test; + +internal class SecondTestService +{ + private readonly IFirstTestService firstTestService; + private int count = 0; + + public SecondTestService(IFirstTestService firstTestService) + { + this.firstTestService = firstTestService; + } + + public void Output() + { + firstTestService.Print(); + Console.WriteLine(nameof(SecondTestService) + " | " + count++); + } +} \ No newline at end of file diff --git a/DynamicInjector-Test/TestClass.cs b/DynamicInjector-Test/TestClass.cs new file mode 100644 index 0000000..dfc9de7 --- /dev/null +++ b/DynamicInjector-Test/TestClass.cs @@ -0,0 +1,28 @@ +using DynamicInjector; + +namespace DynamicInjector_Test; + +internal class TestClass +{ + private readonly IFirstTestService testService; + + [Inject] + private SecondTestService? SecondTestService { get; set; } + + public TestClass(IFirstTestService yes) + { + testService = yes; + } + + public void Test() + { + testService.Print(); + SecondTestService?.Output(); + } + + public int SecondTest(SecondTestService secondTestService) + { + secondTestService.Output(); + return 100; + } +} \ No newline at end of file diff --git a/DynamicInjector.sln b/DynamicInjector.sln new file mode 100644 index 0000000..5c25e66 --- /dev/null +++ b/DynamicInjector.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31912.275 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicInjector", "DynamicInjector\DynamicInjector.csproj", "{B5C9B128-72FD-4278-AC8F-24B948F00679}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicInjector-Test", "DynamicInjector-Test\DynamicInjector-Test.csproj", "{E5415270-33A1-4BB7-8BC5-04E036461F36}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B5C9B128-72FD-4278-AC8F-24B948F00679}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5C9B128-72FD-4278-AC8F-24B948F00679}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5C9B128-72FD-4278-AC8F-24B948F00679}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5C9B128-72FD-4278-AC8F-24B948F00679}.Release|Any CPU.Build.0 = Release|Any CPU + {E5415270-33A1-4BB7-8BC5-04E036461F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5415270-33A1-4BB7-8BC5-04E036461F36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5415270-33A1-4BB7-8BC5-04E036461F36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5415270-33A1-4BB7-8BC5-04E036461F36}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EBB4F955-9160-48BC-B0EE-B9156D360682} + EndGlobalSection +EndGlobal diff --git a/DynamicInjector/DynamicInjector.csproj b/DynamicInjector/DynamicInjector.csproj new file mode 100644 index 0000000..8ef8970 --- /dev/null +++ b/DynamicInjector/DynamicInjector.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.1 + enable + + + diff --git a/DynamicInjector/InjectAttribute.cs b/DynamicInjector/InjectAttribute.cs new file mode 100644 index 0000000..2bba137 --- /dev/null +++ b/DynamicInjector/InjectAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace DynamicInjector +{ + [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + public class InjectAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/DynamicInjector/InjectionContainer.cs b/DynamicInjector/InjectionContainer.cs new file mode 100644 index 0000000..22ca788 --- /dev/null +++ b/DynamicInjector/InjectionContainer.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace DynamicInjector +{ + public class InjectionContainer + { + private readonly Dictionary globalServices = new Dictionary(); + private readonly Dictionary localServices = new Dictionary(); + + public void RegisterGlobal(string? name = null) + { + globalServices.Add(name ?? typeof(TService).FullName, Resolve()!); + } + + public void RegisterGlobal(string? name = null) where TImplementation : TService + { + globalServices.Add(name ?? typeof(TService).FullName, Resolve()!); + } + + public void RegisterGlobal(TImplementation implementation, string? name = null) where TImplementation : TService + { + if (implementation is null) + { + throw new ArgumentNullException(nameof(implementation)); + } + + globalServices.Add(name ?? typeof(TService).FullName, implementation); + } + + public void RegisterLocal(string? name = null) + { + localServices.Add(name ?? typeof(TService).FullName, typeof(TService)); + } + + public void RegisterLocal(string? name = null) where TImplementation : TService + { + localServices.Add(name ?? typeof(TService).FullName, typeof(TService)); + } + + public T Resolve() + { + return (T)Resolve(typeof(T)); + } + + public object Resolve(Type type) + { + ConstructorInfo[]? constructorInfos = type.GetConstructors(); + + if (constructorInfos.Length == 0) + { + return Activator.CreateInstance(type); + } + + if (constructorInfos.Length > 1) + { + throw new InvalidOperationException($"Service \"{type.FullName}\" contains multiple constructors!"); + } + + ConstructorInfo constructorInfo = constructorInfos[0]; + ParameterInfo[] parameterInfos = constructorInfo.GetParameters(); + PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + if (parameterInfos.Length == 0) + { + return Activator.CreateInstance(type); + } + + List arguments = new List(); + + foreach (ParameterInfo parameterInfo in parameterInfos) + { + if (CheckIfServiceExists(parameterInfo.Name, out object? serviceInstance)) + { + arguments.Add(serviceInstance!); + } + else if (CheckIfServiceExists(parameterInfo.ParameterType.FullName, out serviceInstance)) + { + arguments.Add(serviceInstance!); + } + else + { + throw new InvalidOperationException($"Service \"{parameterInfo.ParameterType.FullName}\" not found!"); + } + } + + object instance = Activator.CreateInstance(type, arguments.ToArray()); + + foreach (PropertyInfo propertyInfo in propertyInfos) + { + if (propertyInfo.GetCustomAttribute() == null) + { + continue; + } + + if (CheckIfServiceExists(propertyInfo.Name, out object? serviceInstance)) + { + propertyInfo.SetValue(instance, serviceInstance); + } + else if (CheckIfServiceExists(propertyInfo.PropertyType.FullName, out serviceInstance)) + { + propertyInfo.SetValue(instance, serviceInstance); + } + else + { + throw new InvalidOperationException($"Service \"{propertyInfo.PropertyType.FullName}\" not found!"); + } + } + + return instance; + } + + public object Invoke(TInstance instance, Func method) + { + Delegate delegateMethod = method.Invoke(instance); + MethodInfo methodInfo = delegateMethod.Method; + ParameterInfo[] parameterInfos = methodInfo.GetParameters(); + + if (parameterInfos.Length == 0) + { + return delegateMethod.DynamicInvoke(); + } + + List arguments = new List(); + + foreach (ParameterInfo parameterInfo in parameterInfos) + { + if (CheckIfServiceExists(parameterInfo.Name, out object? serviceInstance)) + { + arguments.Add(serviceInstance!); + } + else if (CheckIfServiceExists(parameterInfo.ParameterType.FullName, out serviceInstance)) + { + arguments.Add(serviceInstance!); + } + else + { + throw new InvalidOperationException($"Service \"{parameterInfo.ParameterType.FullName}\" not found!"); + } + } + + return delegateMethod.DynamicInvoke(arguments.ToArray()); + } + + private bool CheckIfServiceExists(string typeName, out object? serviceInstance) + { + foreach (KeyValuePair serviceType in globalServices) + { + if (typeName == serviceType.Key) + { + serviceInstance = globalServices[serviceType.Key]; + return true; + } + } + + foreach (KeyValuePair serviceType in localServices) + { + if (typeName == serviceType.Key) + { + serviceInstance = Resolve(localServices[serviceType.Key]); + return true; + } + } + + serviceInstance = null; + return false; + } + } +} \ No newline at end of file diff --git a/DynamicInjector/MethodInjectionContainerExtensions.cs b/DynamicInjector/MethodInjectionContainerExtensions.cs new file mode 100644 index 0000000..2724525 --- /dev/null +++ b/DynamicInjector/MethodInjectionContainerExtensions.cs @@ -0,0 +1,82 @@ +using System; + +namespace DynamicInjector +{ + public static class MethodInjectionContainerExtensions + { + public static void Invoke(this InjectionContainer injectionContainer, Func method) + { + injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, TInstance instance, Func> method) + { + return (TReturn)injectionContainer.Invoke(instance, method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, TInstance instance, Func> method) + { + return (TReturn)injectionContainer.Invoke(instance, method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, TInstance instance, Func> method) + { + return (TReturn)injectionContainer.Invoke(instance, method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, TInstance instance, Func> method) + { + return (TReturn)injectionContainer.Invoke(instance, method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, TInstance instance, Func> method) + { + return (TReturn)injectionContainer.Invoke(instance, method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, TInstance instance, Func> method) + { + return (TReturn)injectionContainer.Invoke(instance, method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, TInstance instance, Func> method) + { + return (TReturn)injectionContainer.Invoke(instance, method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, Func> method) + { + return (TReturn)injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, Func> method) + { + return (TReturn)injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, Func> method) + { + return (TReturn)injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, Func> method) + { + return (TReturn)injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, Func> method) + { + return (TReturn)injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, Func> method) + { + return (TReturn)injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + + public static TReturn Invoke(this InjectionContainer injectionContainer, Func> method) + { + return (TReturn)injectionContainer.Invoke(injectionContainer.Resolve(), method); + } + } +} \ No newline at end of file