diff --git a/AutoDI.AspNetCore/WebHostBuilderMixins.cs b/AutoDI.AspNetCore/WebHostBuilderMixins.cs index 0ce7f8d..b273324 100644 --- a/AutoDI.AspNetCore/WebHostBuilderMixins.cs +++ b/AutoDI.AspNetCore/WebHostBuilderMixins.cs @@ -1,6 +1,6 @@ using System.Reflection; using Microsoft.AspNetCore.Hosting; -[assembly:AutoDI.Settings(AutoInit = false)] +[assembly:AutoDI.Settings(InitMode = AutoDI.InitMode.None)] namespace AutoDI.AspNetCore { public static class WebHostBuilderMixins diff --git a/AutoDI.Build.Tests/ManualInjectionOfContainer.cs b/AutoDI.Build.Tests/ManualInjectionOfContainer.cs index 9ddafcd..a6737b2 100644 --- a/AutoDI.Build.Tests/ManualInjectionOfContainer.cs +++ b/AutoDI.Build.Tests/ManualInjectionOfContainer.cs @@ -48,7 +48,7 @@ public void CanManuallyInjectTheGeneratedContainer() // // // -// +// namespace ManualInjectionNamespace { using AutoDI; diff --git a/AutoDI.Build.Tests/ModuleLoadInjectionTests.cs b/AutoDI.Build.Tests/ModuleLoadInjectionTests.cs new file mode 100644 index 0000000..a57bf48 --- /dev/null +++ b/AutoDI.Build.Tests/ModuleLoadInjectionTests.cs @@ -0,0 +1,80 @@ +using AutoDI.AssemblyGenerator; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ModuleLoadInjectionNamespace; +using System.Reflection; +using System.Threading.Tasks; + +namespace AutoDI.Build.Tests +{ + [TestClass] + public class ModuleLoadInjectionTests + { + private static Assembly _testAssembly; + + [ClassInitialize] + public static async Task Initialize(TestContext context) + { + var gen = new Generator(); + + _testAssembly = (await gen.Execute()).SingleAssembly(); + } + + [ClassCleanup] + public static void Cleanup() + { + DI.Dispose(_testAssembly); + } + + [TestMethod] + public void InitThrowsIfModuleLoadAssembly() + { + try + { + DI.Init(_testAssembly); + } + catch (TargetInvocationException e) + when (e.InnerException is AlreadyInitializedException) + { + return; + } + Assert.Fail($"Excepted {nameof(AlreadyInitializedException)}"); + } + + [TestMethod] + public void TryInitReturnsFalseOnFirstInvocation() + { + Assert.IsFalse(DI.TryInit(_testAssembly)); + } + + [TestMethod] + public void LibraryDependenciesAreInjected() + { + dynamic sut = _testAssembly.CreateInstance(); + Assert.IsTrue(((object)sut.Service).Is()); + } + } +} + +// +// +// +// +namespace ModuleLoadInjectionNamespace +{ + using AutoDI; + + public class ModuleLoadingLibrary + { + public IService Service { get; } + + public ModuleLoadingLibrary([Dependency] IService service = null) + { + Service = service; + } + } + + public interface IService { } + + public class Service : IService { } +} +// diff --git a/AutoDI.Build.Tests/SettingsTests.cs b/AutoDI.Build.Tests/SettingsTests.cs index d3c74bf..2b833ee 100644 --- a/AutoDI.Build.Tests/SettingsTests.cs +++ b/AutoDI.Build.Tests/SettingsTests.cs @@ -20,5 +20,13 @@ public void DebugCodeGenerationDefaultsOff() Assert.AreEqual((int)CodeLanguage.None, (int)settings.DebugCodeGeneration); } + + [TestMethod] + public void InitModeDefaultsEntryPoint() + { + var settings = new Settings(); + + Assert.AreEqual((int)InitMode.EntryPoint, (int)settings.InitMode); + } } } diff --git a/AutoDI.Build/AutoDI.Build.csproj b/AutoDI.Build/AutoDI.Build.csproj index 5218cba..f6e3f08 100644 --- a/AutoDI.Build/AutoDI.Build.csproj +++ b/AutoDI.Build/AutoDI.Build.csproj @@ -28,6 +28,7 @@ + diff --git a/AutoDI.Build/ProcessAssemblyTask.InjectContainer.cs b/AutoDI.Build/ProcessAssemblyTask.InjectContainer.cs index c701c29..760a958 100644 --- a/AutoDI.Build/ProcessAssemblyTask.InjectContainer.cs +++ b/AutoDI.Build/ProcessAssemblyTask.InjectContainer.cs @@ -1,4 +1,5 @@ -using Mono.Cecil; +using System.Linq; +using Mono.Cecil; using Mono.Cecil.Cil; namespace AutoDI.Build @@ -20,5 +21,47 @@ private void InjectInitCall(MethodReference initMethod) Logger.Debug($"No entry point in {ModuleDefinition.FileName}. Skipping container injection.", DebugLogLevel.Default); } } + + private void InjectModuleCctorInitCall(MethodReference initMethod) + { + var moduleClass = ModuleDefinition.Types.FirstOrDefault(t => t.Name == ""); + if (moduleClass == null) + { + Logger.Debug($"No module class in {ModuleDefinition.FileName}. Skipping container injection.", DebugLogLevel.Default); + return; + } + + var cctor = FindOrCreateCctor(moduleClass); + if (cctor != null) + { + Logger.Debug("Injecting AutoDI .cctor init call", DebugLogLevel.Verbose); + + var injector = new Injector(cctor); + injector.Insert(OpCodes.Ldnull); + injector.Insert(OpCodes.Call, initMethod); + } + else + { + Logger.Debug($"Couldn't find or create .cctor in {ModuleDefinition.FileName}. Skipping container injection.", DebugLogLevel.Default); + } + + } + + private MethodDefinition FindOrCreateCctor(TypeDefinition moduleClass) + { + var cctor = moduleClass.Methods.FirstOrDefault(m => m.Name == ".cctor"); + if (cctor == null) + { + var attributes = MethodAttributes.Private + | MethodAttributes.HideBySig + | MethodAttributes.Static + | MethodAttributes.SpecialName + | MethodAttributes.RTSpecialName; + cctor = new MethodDefinition(".cctor", attributes, ModuleDefinition.ImportReference(typeof(void))); + moduleClass.Methods.Add(cctor); + cctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + } + return cctor; + } } } \ No newline at end of file diff --git a/AutoDI.Build/ProcessAssemblyTask.cs b/AutoDI.Build/ProcessAssemblyTask.cs index ff6d051..2277bb0 100644 --- a/AutoDI.Build/ProcessAssemblyTask.cs +++ b/AutoDI.Build/ProcessAssemblyTask.cs @@ -60,9 +60,20 @@ protected override bool WeaveAssembly() ModuleDefinition.Types.Add(GenerateAutoDIClass(mapping, settings, gen, out MethodDefinition initMethod)); - if (settings.AutoInit) + switch (settings.InitMode) { - InjectInitCall(initMethod); + case InitMode.None: + Logger.Debug("Skipping injections of Init method", DebugLogLevel.Verbose); + break; + case InitMode.EntryPoint: + InjectInitCall(initMethod); + break; + case InitMode.ModuleLoad: + InjectModuleCctorInitCall(initMethod); + break; + default: + Logger.Warning($"Unsupported InitMode: {settings.InitMode}"); + break; } } else diff --git a/AutoDI.Build/Settings.cs b/AutoDI.Build/Settings.cs index e13e8ee..9418ad1 100644 --- a/AutoDI.Build/Settings.cs +++ b/AutoDI.Build/Settings.cs @@ -21,8 +21,8 @@ public static Settings Load(ModuleDefinition module) { switch (property.Name) { - case nameof(SettingsAttribute.AutoInit): - settings.AutoInit = (bool)property.Argument.Value; + case nameof(SettingsAttribute.InitMode): + settings.InitMode = (InitMode)property.Argument.Value; break; case nameof(SettingsAttribute.Behavior): settings.Behavior = (Behaviors)property.Argument.Value; @@ -209,12 +209,12 @@ IList GetConstructorParameterNames() public Behaviors Behavior { get; set; } = Behaviors.Default; /// - /// Automatically initialize AutoDI in assembly entry point (if available) + /// Specifies if and when AutoDI initializes during assembly loading. /// - public bool AutoInit { get; set; } = true; + public InitMode InitMode { get; set; } = InitMode.EntryPoint; /// - /// Generate registration calls no the container. Setting to false will negate AutoInit. + /// Generate registration calls no the container. Setting to false will negate InitMode. /// public bool GenerateRegistrations { get; set; } = true; @@ -236,7 +236,7 @@ public override string ToString() sb.AppendLine("AutoDI Settings:"); sb.AppendLine($" Behavior(s): {Behavior}"); - sb.AppendLine($" AutoInit: {AutoInit}"); + sb.AppendLine($" InitMode: {InitMode}"); sb.AppendLine($" GenerateRegistrations: {GenerateRegistrations}"); sb.AppendLine($" DebugLogLevel: {DebugLogLevel}"); sb.AppendLine($" DebugExceptions: {DebugExceptions}"); diff --git a/AutoDI/InitMode.cs b/AutoDI/InitMode.cs new file mode 100644 index 0000000..e7d0b0e --- /dev/null +++ b/AutoDI/InitMode.cs @@ -0,0 +1,23 @@ +namespace AutoDI +{ + /// + /// Controls when AutoDI is initialized + /// + public enum InitMode + { + /// + /// The container must be initialized manually. + /// + None, + /// + /// The container will be initialized on your program's entry point. + /// Recommended for executable programs. + /// + EntryPoint, + /// + /// The container will be initialized on assembly module load. + /// Recommended for libraries. + /// + ModuleLoad + } +} diff --git a/AutoDI/SettingsAttribute.cs b/AutoDI/SettingsAttribute.cs index 3d41dc7..48792e1 100644 --- a/AutoDI/SettingsAttribute.cs +++ b/AutoDI/SettingsAttribute.cs @@ -6,7 +6,7 @@ namespace AutoDI public class SettingsAttribute : Attribute { public Behaviors Behavior { get; set; } - public bool AutoInit { get; set; } + public InitMode InitMode { get; set; } public bool GenerateRegistrations { get; set; } public bool DebugExceptions { get; set; } public DebugLogLevel DebugLogLevel { get; set; }