Skip to content

Commit

Permalink
Add ability to inject into module initializer (#165)
Browse files Browse the repository at this point in the history
* Add option to inject into module initializer

* Change AutoInit into InitMode enum

* Add unit tests for InitMode.ModuleLoad
  • Loading branch information
hymccord authored and Keboo committed Oct 5, 2019
1 parent 5a9a49c commit 02e7fbd
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 12 deletions.
2 changes: 1 addition & 1 deletion AutoDI.AspNetCore/WebHostBuilderMixins.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion AutoDI.Build.Tests/ManualInjectionOfContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void CanManuallyInjectTheGeneratedContainer()
//<type:ConsoleApplication/>
//<ref: AutoDI />
//<weaver: AutoDI.Build.ProcessAssemblyTask />
//<raw:[assembly:AutoDI.Settings(AutoInit = false)] />
//<raw:[assembly:AutoDI.Settings(InitMode = AutoDI.InitMode.None)] />
namespace ManualInjectionNamespace
{
using AutoDI;
Expand Down
80 changes: 80 additions & 0 deletions AutoDI.Build.Tests/ModuleLoadInjectionTests.cs
Original file line number Diff line number Diff line change
@@ -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<ModuleLoadingLibrary>();
Assert.IsTrue(((object)sut.Service).Is<Service>());
}
}
}

//<assembly />
//<ref: AutoDI />
//<weaver: AutoDI.Build.ProcessAssemblyTask />
//<raw:[assembly:AutoDI.Settings(InitMode = AutoDI.InitMode.ModuleLoad)] />
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 { }
}
//</assembly>
8 changes: 8 additions & 0 deletions AutoDI.Build.Tests/SettingsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
1 change: 1 addition & 0 deletions AutoDI.Build/AutoDI.Build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Compile Include="..\AutoDI\Constants.cs" Link="Constants.cs" />
<Compile Include="..\AutoDI\DebugLogLevel.cs" Link="DebugLogLevel.cs" />
<Compile Include="..\AutoDI\IncludeAssemblyAttribute.cs" Link="IncludeAssemblyAttribute.cs" />
<Compile Include="..\AutoDI\InitMode.cs" Link="InitMode.cs" />
<Compile Include="..\AutoDI\Lifetime.cs" Link="Lifetime.cs" />
<Compile Include="..\AutoDI\MapAttribute.cs" Link="MapAttribute.cs" />
<Compile Include="..\AutoDI\SettingsAttribute.cs" Link="SettingsAttribute.cs" />
Expand Down
45 changes: 44 additions & 1 deletion AutoDI.Build/ProcessAssemblyTask.InjectContainer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Mono.Cecil;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace AutoDI.Build
Expand All @@ -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 == "<Module>");
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;
}
}
}
15 changes: 13 additions & 2 deletions AutoDI.Build/ProcessAssemblyTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions AutoDI.Build/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -209,12 +209,12 @@ IList<string> GetConstructorParameterNames()
public Behaviors Behavior { get; set; } = Behaviors.Default;

/// <summary>
/// Automatically initialize AutoDI in assembly entry point (if available)
/// Specifies if and when AutoDI initializes during assembly loading.
/// </summary>
public bool AutoInit { get; set; } = true;
public InitMode InitMode { get; set; } = InitMode.EntryPoint;

/// <summary>
/// Generate registration calls no the container. Setting to false will negate AutoInit.
/// Generate registration calls no the container. Setting to false will negate InitMode.
/// </summary>
public bool GenerateRegistrations { get; set; } = true;

Expand All @@ -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}");
Expand Down
23 changes: 23 additions & 0 deletions AutoDI/InitMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace AutoDI
{
/// <summary>
/// Controls when AutoDI is initialized
/// </summary>
public enum InitMode
{
/// <summary>
/// The container must be initialized manually.
/// </summary>
None,
/// <summary>
/// The container will be initialized on your program's entry point.
/// Recommended for executable programs.
/// </summary>
EntryPoint,
/// <summary>
/// The container will be initialized on assembly module load.
/// Recommended for libraries.
/// </summary>
ModuleLoad
}
}
2 changes: 1 addition & 1 deletion AutoDI/SettingsAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down

0 comments on commit 02e7fbd

Please sign in to comment.