Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to inject into module initializer #165

Merged
merged 3 commits into from
Oct 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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