Skip to content

Commit

Permalink
Fix #56 autofac ambiguous stepdef and hook required #127 issue (#139)
Browse files Browse the repository at this point in the history
* Refactor AutofacPlugin and add unit tests

* Add extension method to add bindings from assembly

* fix duplicate registrations bug

* fix typo

* Fix the fix and also #127

* Add CHANGELOG
  • Loading branch information
gasparnagy authored May 21, 2024
1 parent 36d355f commit eef03e4
Show file tree
Hide file tree
Showing 13 changed files with 561 additions and 233 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* Allow creating single target (netstandard2.0) plugins
* MsTest: Use ClassCleanupBehavior.EndOfClass instead of custom implementation (preparation for MsTest v4.0)
* Fix: #71 StackOverflowException when using [StepArgumentTransformation] with same input and output type (for example string)
* Fix: Autofac without hook does not run GlobalDependencies (#127)
* Fix: Reqnroll.Autofac shows wrongly ambiguous step definition (#56)

# v1.0.1 - 2024-02-16

Expand Down
274 changes: 137 additions & 137 deletions Plugins/Reqnroll.Autofac.ReqnrollPlugin/AutofacPlugin.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Reqnroll.Bindings;
using Reqnroll.Infrastructure;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Reqnroll.Autofac;
public class ConfigurationMethodsProvider(ITestAssemblyProvider _testAssemblyProvider) : IConfigurationMethodsProvider
{
public IEnumerable<MethodInfo> GetConfigurationMethods()
{
return _testAssemblyProvider.TestAssembly.GetTypes()
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Reflection;
using Autofac;
using Reqnroll;

namespace Reqnroll.Autofac.ReqnrollPlugin
{
Expand All @@ -13,7 +12,7 @@ public static class ContainerBuilderExtensions
/// <summary>
/// Add Reqnroll binding for classes in the assembly where typeof TAssemblyType resides.
/// </summary>
/// <typeparam name="TAssemblyType">The type in an assembly to search for bindings.</typeparam>
/// <typeparam name="TAssemblyType">Any type in an assembly to search for bindings.</typeparam>
/// <param name="builder">The builder.</param>
/// <returns>The builder.</returns>
public static ContainerBuilder AddReqnrollBindings<TAssemblyType>(this ContainerBuilder builder) => builder.AddReqnrollBindings(typeof(TAssemblyType));
Expand All @@ -22,12 +21,20 @@ public static class ContainerBuilderExtensions
/// Add Reqnroll binding for classes in the assembly where the type resides.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="type">The type in an assembly to search for bindings.</param>
/// <param name="type">Any type in an assembly to search for bindings.</param>
/// <returns>The builder.</returns>
public static ContainerBuilder AddReqnrollBindings(this ContainerBuilder builder, Type type)
public static ContainerBuilder AddReqnrollBindings(this ContainerBuilder builder, Type type) => builder.AddReqnrollBindings(type.Assembly);

/// <summary>
/// Add Reqnroll binding for classes in an assembly.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="assembly">The assembly to search for bindings.</param>
/// <returns>The builder.</returns>
public static ContainerBuilder AddReqnrollBindings(this ContainerBuilder builder, Assembly assembly)
{
builder
.RegisterAssemblyTypes(type.Assembly)
.RegisterAssemblyTypes(assembly)
.Where(t => Attribute.IsDefined(t, typeof(BindingAttribute)))
.SingleInstance();
return builder;
Expand Down
120 changes: 56 additions & 64 deletions Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,77 @@
using System.Reflection;
using Autofac;
using Reqnroll.Autofac.ReqnrollPlugin;
using Reqnroll.Bindings;
using Reqnroll.Bindings.Discovery;
using Reqnroll.Infrastructure;
using ContainerBuilder = Autofac.ContainerBuilder;

namespace Reqnroll.Autofac
namespace Reqnroll.Autofac;

public class ContainerBuilderFinder : IContainerBuilderFinder
{
public class ContainerBuilderFinder : IContainerBuilderFinder
private readonly IConfigurationMethodsProvider _configurationMethodsProvider;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> _createConfigureGlobalContainer;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> _createConfigureScenarioContainer;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> _legacyCreateScenarioContainerBuilder;
private readonly Lazy<Func<ILifetimeScope>> _getFeatureLifetimeScope;

public ContainerBuilderFinder(IConfigurationMethodsProvider configurationMethodsProvider)
{
private readonly IBindingRegistry bindingRegistry;
private readonly IRuntimeBindingRegistryBuilder bindingRegistryBuilder;
private readonly ITestAssemblyProvider testAssemblyProvider;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> createConfigureGlobalContainer;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> createConfigureScenarioContainer;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> createScenarioContainerBuilder;
private readonly Lazy<Func<ILifetimeScope>> getFeatureLifetimeScope;
_configurationMethodsProvider = configurationMethodsProvider;

public ContainerBuilderFinder(IBindingRegistry bindingRegistry, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, ITestAssemblyProvider testAssemblyProvider)
static ContainerBuilder InvokeVoidAndReturnBuilder(ContainerBuilder containerBuilder, MethodInfo methodInfo)
{
this.bindingRegistry = bindingRegistry;
this.bindingRegistryBuilder = bindingRegistryBuilder;
this.testAssemblyProvider = testAssemblyProvider;
static ContainerBuilder invokeVoidAndReturnBuilder(ContainerBuilder containerBuilder, MethodInfo methodInfo)
{
methodInfo.Invoke(null, new[] { containerBuilder });
return containerBuilder;
}
createConfigureGlobalContainer = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(GlobalDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true);
createConfigureScenarioContainer = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true);
createScenarioContainerBuilder = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(ContainerBuilder), (c, m) => (ContainerBuilder)m.Invoke(null, null)), true);
getFeatureLifetimeScope = new Lazy<Func<ILifetimeScope>>(() => FindLifetimeScope(typeof(FeatureDependenciesAttribute), typeof(ILifetimeScope), m => (ILifetimeScope)m.Invoke(null, null)));
methodInfo.Invoke(null, [ containerBuilder ]);
return containerBuilder;
}
_createConfigureGlobalContainer = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(GlobalDependenciesAttribute), typeof(void), InvokeVoidAndReturnBuilder), true);
_createConfigureScenarioContainer = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(void), InvokeVoidAndReturnBuilder), true);
_legacyCreateScenarioContainerBuilder = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(ContainerBuilder), (_, m) => (ContainerBuilder)m.Invoke(null, null)), true);
_getFeatureLifetimeScope = new Lazy<Func<ILifetimeScope>>(() => FindLifetimeScope(typeof(FeatureDependenciesAttribute), typeof(ILifetimeScope), m => (ILifetimeScope)m.Invoke(null, null)));
}

public Func<ContainerBuilder, ContainerBuilder> GetConfigureGlobalContainer()
{
bindingRegistryBuilder.BuildBindingsFromAssembly(testAssemblyProvider.TestAssembly);
return createConfigureGlobalContainer.Value;
}
public Func<ContainerBuilder, ContainerBuilder> GetConfigureGlobalContainer()
{
return _createConfigureGlobalContainer.Value;
}

public Func<ContainerBuilder, ContainerBuilder> GetConfigureScenarioContainer()
{
return createConfigureScenarioContainer.Value;
}
public Func<ContainerBuilder, ContainerBuilder> GetConfigureScenarioContainer()
{
return _createConfigureScenarioContainer.Value;
}

public Func<ContainerBuilder, ContainerBuilder> GetCreateScenarioContainerBuilder()
{
return createScenarioContainerBuilder.Value;
}
// For legacy support: configuration methods that return a container builder.
// It is recommended to use the void methods that get a container builder as a parameter
public Func<ContainerBuilder, ContainerBuilder> GetLegacyCreateScenarioContainerBuilder()
{
return _legacyCreateScenarioContainerBuilder.Value;
}

public Func<ILifetimeScope> GetFeatureLifetimeScope()
{
return getFeatureLifetimeScope.Value;
}
public Func<ILifetimeScope> GetFeatureLifetimeScope()
{
return _getFeatureLifetimeScope.Value;
}

protected virtual Func<ILifetimeScope> FindLifetimeScope(Type attributeType, Type returnType, Func<MethodInfo, ILifetimeScope> invoke)
{
var method = GetMethod(attributeType, returnType);
protected virtual Func<ILifetimeScope> FindLifetimeScope(Type attributeType, Type returnType, Func<MethodInfo, ILifetimeScope> invoke)
{
var method = GetMethod(attributeType, returnType);

return method == null
? null
: () => invoke(method);
}
return method == null
? null
: () => invoke(method);
}

protected virtual Func<ContainerBuilder, ContainerBuilder> FindCreateScenarioContainerBuilder(Type attributeType, Type returnType, Func<ContainerBuilder, MethodInfo, ContainerBuilder> invoke)
{
var method = GetMethod(attributeType, returnType);
protected virtual Func<ContainerBuilder, ContainerBuilder> FindCreateScenarioContainerBuilder(Type attributeType, Type returnType, Func<ContainerBuilder, MethodInfo, ContainerBuilder> invoke)
{
var method = GetMethod(attributeType, returnType);

return method == null
? null
: containerBuilder => invoke(containerBuilder, method);
}
return method == null
? null
: containerBuilder => invoke(containerBuilder, method);
}

private MethodInfo GetMethod(Type attributeType, Type returnType)
{
return bindingRegistry.GetBindingAssemblies()
.SelectMany(x => x.GetTypes())
.SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
.Where(x => x.ReturnType == returnType)
.FirstOrDefault(x => Attribute.IsDefined(x, attributeType));
}
private MethodInfo GetMethod(Type attributeType, Type returnType)
{
return _configurationMethodsProvider.GetConfigurationMethods()
.Where(x => x.ReturnType == returnType)
.FirstOrDefault(x => Attribute.IsDefined(x, attributeType));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;
using System.Reflection;

namespace Reqnroll.Autofac;

public interface IConfigurationMethodsProvider
{
IEnumerable<MethodInfo> GetConfigurationMethods();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public interface IContainerBuilderFinder

Func<ContainerBuilder, ContainerBuilder> GetConfigureGlobalContainer();

Func<ContainerBuilder, ContainerBuilder> GetCreateScenarioContainerBuilder();
Func<ContainerBuilder, ContainerBuilder> GetLegacyCreateScenarioContainerBuilder();

Func<ILifetimeScope> GetFeatureLifetimeScope();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

<RootNamespace>Reqnroll.Autofac</RootNamespace>
</PropertyGroup>

<ItemGroup>
Expand Down
8 changes: 8 additions & 0 deletions Reqnroll/BoDi/IObjectContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ public interface IObjectContainer : IDisposable
/// <param name="name">A name to resolve named instance, otherwise null.</param>
IStrategyRegistration RegisterFactoryAs<TInterface>(Func<IObjectContainer, TInterface> factoryDelegate, string name = null);

/// <summary>
/// Registers an instance produced by <paramref name="factoryDelegate"/>. The delegate will be called only once and the instance it returned will be returned in each resolution.
/// </summary>
/// <typeparam name="TInterface">Interface to register as.</typeparam>
/// <param name="factoryDelegate">The function to run to obtain the instance.</param>
/// <param name="name">A name to resolve named instance, otherwise null.</param>
IStrategyRegistration RegisterFactoryAs<TInterface>(Func<TInterface> factoryDelegate, string name = null);

/// <summary>
/// Resolves an implementation object for an interface or type.
/// </summary>
Expand Down
Loading

0 comments on commit eef03e4

Please sign in to comment.