diff --git a/.gitignore b/.gitignore index 1e9623f2..cbc17b6e 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,5 @@ $RECYCLE.BIN/ /.vs/config/applicationhost.config /.vs/Grace/v15/sqlite3/storage.ide /.vs/Grace/v15/sqlite3/storage.ide-journal +/.vs/Grace/v15 +/.vs/Grace/DesignTimeBuild/.dtbcache diff --git a/appveyor.yml b/appveyor.yml index f1919ef9..19ea81fa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ environment: - build_version: 6.3.3 - Version: $(build_version) + build_version: 6.3.4 + Version: $(build_version)-Nightly%APPVEYOR_BUILD_NUMBER% COVERALLS_REPO_TOKEN: secure: +OWHMxYHaMp6iRNNLZcMZq423PhYWxMky+B2C0p3U8v7tpdoKRMzWZKJ1LuYO60O version: $(build_version)-{build} @@ -24,7 +24,10 @@ after_build: test_script: - cmd: cd tests/Grace.Tests/ - cmd: CodeCoverageAppVeyor.cmd +- sh: dotnet test tests/Grace.Tests/Grace.Tests.csproj artifacts: - path: Grace*.nupkg name: Grace -os: Visual Studio 2017 +image: +- Visual Studio 2017 +- Ubuntu \ No newline at end of file diff --git a/src/Grace.Dynamic/Grace.Dynamic.csproj b/src/Grace.Dynamic/Grace.Dynamic.csproj index f50748be..b9ccf0c2 100644 --- a/src/Grace.Dynamic/Grace.Dynamic.csproj +++ b/src/Grace.Dynamic/Grace.Dynamic.csproj @@ -4,6 +4,7 @@ IL Generation library for Grace dependency injection container Ian Johnson netstandard1.1;net45 + netstandard1.1 true Grace.Dynamic ..\Grace.snk @@ -22,10 +23,21 @@ full + + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + diff --git a/src/Grace.Factory/Grace.Factory.csproj b/src/Grace.Factory/Grace.Factory.csproj index 798b3f8b..de173a4b 100644 --- a/src/Grace.Factory/Grace.Factory.csproj +++ b/src/Grace.Factory/Grace.Factory.csproj @@ -1,9 +1,10 @@ - + IL Generation library for Grace dependency injection container Ian Johnson netstandard1.1;net45 + netstandard1.1 true Grace.Factory ..\Grace.snk @@ -22,10 +23,21 @@ full + + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + diff --git a/src/Grace/DependencyInjection/IExportRegistrationBlock.cs b/src/Grace/DependencyInjection/IExportRegistrationBlock.cs index 74395f12..39908c8d 100644 --- a/src/Grace/DependencyInjection/IExportRegistrationBlock.cs +++ b/src/Grace/DependencyInjection/IExportRegistrationBlock.cs @@ -92,7 +92,7 @@ public interface IExportRegistrationBlock /// decorator logic /// void ExportDecorator(Func apply, bool applyAfterLifestyle = true); - + /// /// Export an expression tree /// diff --git a/src/Grace/DependencyInjection/IExportRegistrationBlockExtensions.cs b/src/Grace/DependencyInjection/IExportRegistrationBlockExtensions.cs index 7832bee9..ab18cc3e 100644 --- a/src/Grace/DependencyInjection/IExportRegistrationBlockExtensions.cs +++ b/src/Grace/DependencyInjection/IExportRegistrationBlockExtensions.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using System.Reflection; using Grace.DependencyInjection.Impl; +using Grace.DependencyInjection.Impl.CompiledStrategies; using Grace.DependencyInjection.Impl.Expressions; namespace Grace.DependencyInjection @@ -117,6 +118,12 @@ public static IFluentExportInstanceConfiguration ExportNamedValue( throw new Exception("This method can only be used on members (i.e. ExportNamedValue(() => SomeProperty))"); } + public static void ExportDecoratorFactory(this IExportRegistrationBlock registrationBlock, + Func factory) + { + registrationBlock.AddActivationStrategy(new CompiledFactoryDecoratorStrategy(factory, registrationBlock.OwningScope)); + } + /// /// Import all members of a specific type and can be filtered /// @@ -280,11 +287,11 @@ public static IExportRegistrationBlock ExcludeTypeFromAutoRegistration(this IExp public static IExportRegistrationBlock ExportInitialize(this IExportRegistrationBlock block, Action initializeAction) { - var func = new Func(instance => - { - initializeAction((T) instance); - return instance; - }); + var func = new Func(instance => + { + initializeAction((T)instance); + return instance; + }); block.AddInspector(new ExportInitializeInspector(func, typeof(T))); diff --git a/src/Grace/DependencyInjection/Impl/CompiledStrategies/CompiledFactoryDecoratorStrategy.cs b/src/Grace/DependencyInjection/Impl/CompiledStrategies/CompiledFactoryDecoratorStrategy.cs new file mode 100644 index 00000000..087423f8 --- /dev/null +++ b/src/Grace/DependencyInjection/Impl/CompiledStrategies/CompiledFactoryDecoratorStrategy.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Grace.DependencyInjection.Impl.Expressions; +using Grace.DependencyInjection.Lifestyle; + +namespace Grace.DependencyInjection.Impl.CompiledStrategies +{ + public class CompiledFactoryDecoratorStrategy : ConfigurableActivationStrategy, ICompiledDecoratorStrategy + { + private Delegate _delegate; + + /// + /// Default constructor + /// + /// + /// owning injection scope + public CompiledFactoryDecoratorStrategy(Delegate @delegate, IInjectionScope injectionScope) : base(typeof(T), injectionScope) + { + _delegate = @delegate; + } + + /// + /// Type of activation strategy + /// + public override ActivationStrategyType StrategyType { get; } = ActivationStrategyType.DecoratorStrategy; + + /// + /// Get an activation expression for this strategy + /// + /// + /// + /// + /// + public IActivationExpressionResult GetDecoratorActivationExpression(IInjectionScope scope, + IActivationExpressionRequest request, ICompiledLifestyle lifestyle) + { + if (lifestyle == null) + { + return InternalGetDecoratorActivationExpression(scope, request); + } + + if (ApplyAfterLifestyle) + { + return lifestyle.ProvideLifestyleExpression( + scope, request, lifestyleRequest => InternalGetDecoratorActivationExpression(scope, lifestyleRequest)); + } + + return lifestyle.ProvideLifestyleExpression( + scope, request, lifestyleRequest => InternalGetDecoratorActivationExpression(scope, request)); + } + + /// + /// Apply the decorator after a lifestyle has been used + /// + public bool ApplyAfterLifestyle { get; set; } + + /// + /// Get decorator expression + /// + /// + /// + /// + protected virtual IActivationExpressionResult InternalGetDecoratorActivationExpression(IInjectionScope scope, IActivationExpressionRequest request) + { + return ExpressionUtilities.CreateExpressionForDelegate(_delegate, false, InjectionScope, request, this); + } + } +} diff --git a/src/Grace/DependencyInjection/Impl/Expressions/ActivationExpressionBuilder.cs b/src/Grace/DependencyInjection/Impl/Expressions/ActivationExpressionBuilder.cs index efaf5d2a..816573d1 100644 --- a/src/Grace/DependencyInjection/Impl/Expressions/ActivationExpressionBuilder.cs +++ b/src/Grace/DependencyInjection/Impl/Expressions/ActivationExpressionBuilder.cs @@ -576,7 +576,8 @@ protected virtual IActivationExpressionResult GetExpressionFromStrategyCollectio { var strategy = request.Filter == null ? collection.GetPrimary() : null; - if (strategy != null) + if (strategy != null && + strategy != request.RequestingStrategy) { var result = ActivationExpressionForStrategy(scope, request, strategy); @@ -622,6 +623,11 @@ protected virtual IActivationExpressionResult SelectStrategyFromCollection(IActi } } + if (request.RequestingStrategy == strategy) + { + continue; + } + result = strategy.GetActivationExpression(scope, request); if (result != null) diff --git a/src/Grace/DependencyInjection/Impl/Expressions/ConstructorExpressionCreator.cs b/src/Grace/DependencyInjection/Impl/Expressions/ConstructorExpressionCreator.cs index 6d2fa15f..6ca6080b 100644 --- a/src/Grace/DependencyInjection/Impl/Expressions/ConstructorExpressionCreator.cs +++ b/src/Grace/DependencyInjection/Impl/Expressions/ConstructorExpressionCreator.cs @@ -208,7 +208,7 @@ protected IActivationExpressionResult CallExportFunc(IActivationStrategy strateg var newRequest = request.NewRequest(parameter.ParameterType, strategy, strategy.ActivationType, RequestType.ConstructorParameter, parameter, false, true); - return ExpressionUtilities.CreateExpressionForDelegate(exportDelegate, ShouldTrackDisposable(configurationExternallyOwned, injectionScope, strategy), injectionScope, newRequest); + return ExpressionUtilities.CreateExpressionForDelegate(exportDelegate, ShouldTrackDisposable(configurationExternallyOwned, injectionScope, strategy), injectionScope, newRequest, strategy); } /// diff --git a/src/Grace/DependencyInjection/Impl/Expressions/DisposalScopeExpressionCreator.cs b/src/Grace/DependencyInjection/Impl/Expressions/DisposalScopeExpressionCreator.cs index 8fd47191..f850210f 100644 --- a/src/Grace/DependencyInjection/Impl/Expressions/DisposalScopeExpressionCreator.cs +++ b/src/Grace/DependencyInjection/Impl/Expressions/DisposalScopeExpressionCreator.cs @@ -74,25 +74,16 @@ public IActivationExpressionResult CreateExpression(IInjectionScope scope, IActi /// /// Method info for add method on IDisposalScope /// - protected MethodInfo AddMethod - { - get - { - return _addMethod ?? - (_addMethod = typeof(IDisposalScope).GetTypeInfo().DeclaredMethods.First(m => m.Name == "AddDisposable" && m.GetParameters().Length == 1)); - } - } + protected MethodInfo AddMethod=> _addMethod ?? + (_addMethod = typeof(IDisposalScope).GetTypeInfo().DeclaredMethods.First(m => m.Name == "AddDisposable" && + m.GetParameters().Length == 1)); + /// /// Method info for add method on IDisposalScope with cleanup delegate /// - protected MethodInfo AddMethodWithCleanup - { - get - { - return _addMethod ?? - (_addMethod = typeof(IDisposalScope).GetTypeInfo().DeclaredMethods.First(m => m.Name == "AddDisposable" && m.GetParameters().Length == 2)); - } - } + protected MethodInfo AddMethodWithCleanup => _addMethod ?? + (_addMethod = typeof(IDisposalScope).GetTypeInfo().DeclaredMethods.First(m => m.Name == "AddDisposable" && + m.GetParameters().Length == 2)); } } diff --git a/src/Grace/DependencyInjection/Impl/Expressions/DynamicConstructorExpressionCreator.cs b/src/Grace/DependencyInjection/Impl/Expressions/DynamicConstructorExpressionCreator.cs index 06e2c4f2..5da7ee9e 100644 --- a/src/Grace/DependencyInjection/Impl/Expressions/DynamicConstructorExpressionCreator.cs +++ b/src/Grace/DependencyInjection/Impl/Expressions/DynamicConstructorExpressionCreator.cs @@ -47,7 +47,7 @@ public override IActivationExpressionResult CreateExpression(IInjectionScope sco protected virtual IActivationExpressionResult CreateCallExpression(IInjectionScope scope, IActivationExpressionRequest request, TypeActivationConfiguration activationConfiguration, ActivationStrategyDelegate activationDelegate) { return ExpressionUtilities.CreateExpressionForDelegate(activationDelegate, activationConfiguration.ExternallyOwned, - scope, request); + scope, request, activationConfiguration.ActivationStrategy); } /// @@ -220,7 +220,7 @@ private Expression CreateLocateExpression(ParameterInfo parameter, IInjectionSco var delegateValue = (Delegate)parameterInfo.ExportFunc; var expressionCall = ExpressionUtilities.CreateExpressionForDelegate(delegateValue, - activationConfiguration.ExternallyOwned, scope, newRequest); + activationConfiguration.ExternallyOwned, scope, newRequest, activationConfiguration.ActivationStrategy); var standardParameters = delegateValue.GetMethodInfo() .GetParameters() @@ -255,7 +255,7 @@ private Expression CreateLocateExpression(ParameterInfo parameter, IInjectionSco var compiledDelegate = request.Services.Compiler.CompileDelegate(scope, expressionCall); expressionCall = - ExpressionUtilities.CreateExpressionForDelegate(compiledDelegate, false, scope, newRequest); + ExpressionUtilities.CreateExpressionForDelegate(compiledDelegate, false, scope, newRequest, activationConfiguration.ActivationStrategy); return expressionCall.Expression; } diff --git a/src/Grace/DependencyInjection/Impl/Expressions/ExpressionUtilities.cs b/src/Grace/DependencyInjection/Impl/Expressions/ExpressionUtilities.cs index 9b644cca..0fa114f0 100644 --- a/src/Grace/DependencyInjection/Impl/Expressions/ExpressionUtilities.cs +++ b/src/Grace/DependencyInjection/Impl/Expressions/ExpressionUtilities.cs @@ -49,9 +49,10 @@ public static IActivationExpressionResult[] CreateExpressionsForTypes(IActivatio /// /// /// + /// /// public static IActivationExpressionResult CreateExpressionForDelegate(Delegate delegateInstance, bool allowDisposableTracking, IInjectionScope scope, - IActivationExpressionRequest request) + IActivationExpressionRequest request, IActivationStrategy requestingStrategy) { var methodInfo = delegateInstance.GetMethodInfo(); @@ -61,7 +62,7 @@ public static IActivationExpressionResult CreateExpressionForDelegate(Delegate d // Handle closure based delegates differently if (delegateInstance.Target != null && delegateInstance.Target.GetType().FullName == _closureName) { - resultsExpressions = CreateExpressionsForTypes(request.RequestingStrategy, scope, request, methodInfo.ReturnType, + resultsExpressions = CreateExpressionsForTypes(requestingStrategy, scope, request, methodInfo.ReturnType, methodInfo.GetParameters(). Where(p => !(p.Position == 0 && p.ParameterType.FullName == "System.Runtime.CompilerServices.Closure")). Select(p => p.ParameterType).ToArray()); @@ -72,7 +73,7 @@ public static IActivationExpressionResult CreateExpressionForDelegate(Delegate d } else { - resultsExpressions = CreateExpressionsForTypes(request.RequestingStrategy, scope, request, methodInfo.ReturnType, + resultsExpressions = CreateExpressionsForTypes(requestingStrategy, scope, request, methodInfo.ReturnType, methodInfo.GetParameters().Select(p => p.ParameterType).ToArray()); expression = methodInfo.IsStatic diff --git a/src/Grace/DependencyInjection/Impl/Expressions/WrapperExpressionCreator.cs b/src/Grace/DependencyInjection/Impl/Expressions/WrapperExpressionCreator.cs index ca4d519e..60251a41 100644 --- a/src/Grace/DependencyInjection/Impl/Expressions/WrapperExpressionCreator.cs +++ b/src/Grace/DependencyInjection/Impl/Expressions/WrapperExpressionCreator.cs @@ -181,7 +181,7 @@ public bool SetupWrappersForRequest(IInjectionScope scope, IActivationExpression { var primary = request.Filter == null ? collection.GetPrimary() : null; - if (primary != null) + if (primary != null && primary != request.RequestingStrategy) { wrappers = ImmutableLinkedList.Empty .Add(new WrapperActivationPathNode(primary, wrappedType, null)) @@ -207,6 +207,7 @@ public bool SetupWrappersForRequest(IInjectionScope scope, IActivationExpression } if (pass && + request.RequestingStrategy != strategy && (request.Filter == null || request.Filter(strategy))) { wrappers = ImmutableLinkedList.Empty diff --git a/src/Grace/DependencyInjection/Impl/FluentDecoratorStrategyConfiguration.cs b/src/Grace/DependencyInjection/Impl/FluentDecoratorStrategyConfiguration.cs index 313c26de..76e0721f 100644 --- a/src/Grace/DependencyInjection/Impl/FluentDecoratorStrategyConfiguration.cs +++ b/src/Grace/DependencyInjection/Impl/FluentDecoratorStrategyConfiguration.cs @@ -18,8 +18,7 @@ public FluentDecoratorStrategyConfiguration(ICompiledDecoratorStrategy strategy) { _strategy = strategy; } - - + /// /// Apply decorator after lifestyle, by default it's before /// diff --git a/src/Grace/DependencyInjection/Impl/InjectionContextValueProvider.cs b/src/Grace/DependencyInjection/Impl/InjectionContextValueProvider.cs index 4d79e529..ce091120 100644 --- a/src/Grace/DependencyInjection/Impl/InjectionContextValueProvider.cs +++ b/src/Grace/DependencyInjection/Impl/InjectionContextValueProvider.cs @@ -52,7 +52,7 @@ public class InjectionContextValueProvider : IInjectionContextValueProvider /// /// Get data from injection context /// - /// + /// /// /// /// diff --git a/src/Grace/DependencyInjection/Impl/InstanceStrategies/DelegateBaseExportStrategy.cs b/src/Grace/DependencyInjection/Impl/InstanceStrategies/DelegateBaseExportStrategy.cs index 44e56a99..5b735284 100644 --- a/src/Grace/DependencyInjection/Impl/InstanceStrategies/DelegateBaseExportStrategy.cs +++ b/src/Grace/DependencyInjection/Impl/InstanceStrategies/DelegateBaseExportStrategy.cs @@ -54,7 +54,7 @@ protected override IActivationExpressionResult CreateExpression(IInjectionScope protected virtual IActivationExpressionResult CreateExpression(IInjectionScope scope, IActivationExpressionRequest request) { - return ExpressionUtilities.CreateExpressionForDelegate(DelegateInstance, ShouldTrackDisposable(scope), scope, request); + return ExpressionUtilities.CreateExpressionForDelegate(DelegateInstance, ShouldTrackDisposable(scope), scope, request, this); } private bool ShouldTrackDisposable(IInjectionScope scope) diff --git a/src/Grace/Grace.csproj b/src/Grace/Grace.csproj index 49f208b8..2dea23e3 100644 --- a/src/Grace/Grace.csproj +++ b/src/Grace/Grace.csproj @@ -4,6 +4,7 @@ Grace is a feature rich Dependency Injection Container Ian Johnson netstandard1.0;net45 + netstandard1.0 true Grace ..\Grace.snk @@ -23,6 +24,17 @@ full + + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + diff --git a/tests/Grace.Tests/DependencyInjection/ConstructorSelection/TimedConstructorSelectionMethod.cs b/tests/Grace.Tests/DependencyInjection/ConstructorSelection/TimedConstructorSelectionMethod.cs index f29fe455..af0b3dda 100644 --- a/tests/Grace.Tests/DependencyInjection/ConstructorSelection/TimedConstructorSelectionMethod.cs +++ b/tests/Grace.Tests/DependencyInjection/ConstructorSelection/TimedConstructorSelectionMethod.cs @@ -28,7 +28,7 @@ public override IActivationExpressionResult CreateExpression(IInjectionScope sco var createDelegate = request.Services.Compiler.CompileDelegate(scope, timedCreateExpression); - return ExpressionUtilities.CreateExpressionForDelegate(createDelegate, false, scope, request); + return ExpressionUtilities.CreateExpressionForDelegate(createDelegate, false, scope, request, activationConfiguration.ActivationStrategy); } private IActivationExpressionResult CreateTimedCreateExpression(IInjectionScope scope, IActivationExpressionRequest request, TypeActivationConfiguration activationConfiguration, IActivationExpressionResult expression) diff --git a/tests/Grace.Tests/DependencyInjection/Misc/CompositePatternTests.cs b/tests/Grace.Tests/DependencyInjection/Misc/CompositePatternTests.cs new file mode 100644 index 00000000..786b6de3 --- /dev/null +++ b/tests/Grace.Tests/DependencyInjection/Misc/CompositePatternTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Grace.DependencyInjection; +using Xunit; + +namespace Grace.Tests.DependencyInjection.Misc +{ + public class CompositePatternTests + { + public class TestContext + { + public Guid Id { get; set; } = Guid.Empty; + } + + [Fact] + public void CompositeFactoryTest() + { + var container = new DependencyInjectionContainer(); + + container.Configure(c => + { + c.Export(); + c.ExportFactory(context => + { + context.Id = Guid.NewGuid(); + return context; + }); + }); + + var instance = container.Locate(); + + Assert.NotNull(instance); + Assert.NotEqual(instance.Id, Guid.Empty); + } + + + [Fact] + public void CompositeFactoryFuncTest() + { + var container = new DependencyInjectionContainer(); + + container.Configure(c => + { + c.Export(); + c.ExportFactory, TestContext>(func => + { + var context = func(); + context.Id = Guid.NewGuid(); + return context; + }); + }); + + var instance = container.Locate(); + + Assert.NotNull(instance); + Assert.NotEqual(instance.Id, Guid.Empty); + } + + + [Fact] + public void CompositeDecoratorFactoryFuncTest() + { + var container = new DependencyInjectionContainer(); + + container.Configure(c => + { + c.Export(); + c.ExportDecoratorFactory, TestContext>(func => + { + var context = func(); + context.Id = Guid.NewGuid(); + return context; + }); + }); + + var instance = container.Locate(); + + Assert.NotNull(instance); + Assert.NotEqual(instance.Id, Guid.Empty); + } + } +} diff --git a/tests/Grace.Tests/Grace.Tests.csproj b/tests/Grace.Tests/Grace.Tests.csproj index 3798e09a..a73cb576 100644 --- a/tests/Grace.Tests/Grace.Tests.csproj +++ b/tests/Grace.Tests/Grace.Tests.csproj @@ -2,6 +2,7 @@ netcoreapp1.0;net452 + netcoreapp1.0 Grace.Tests Grace.Tests true