diff --git a/src/stashbox.sln b/src/stashbox.sln index eff9de9a..fb88231a 100644 --- a/src/stashbox.sln +++ b/src/stashbox.sln @@ -48,4 +48,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/src/stashbox.tests/BuildUpTests.cs b/src/stashbox.tests/BuildUpTests.cs index ea4db50b..ba348fa8 100644 --- a/src/stashbox.tests/BuildUpTests.cs +++ b/src/stashbox.tests/BuildUpTests.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Stashbox.Attributes; using Stashbox.Utils; @@ -29,11 +30,50 @@ public void BuildUpTests_BuildUp() } } - public interface ITest { } + [TestMethod] + public void BuildUpTests_BuildUp_Scoped() + { + using (var container = new StashboxContainer()) + { + container.RegisterScoped(); + + var test1 = new Test1(); + var test2 = new Test2(); + using (var scope = container.BeginScope()) + { + + container.WireUpAs(test1); + var inst = scope.BuildUp(test2); + + Assert.AreEqual(test2, inst); + Assert.IsNotNull(inst); + Assert.IsNotNull(inst.Test1); + Assert.IsInstanceOfType(inst, typeof(Test2)); + Assert.IsInstanceOfType(inst.Test1, typeof(Test1)); + Assert.IsInstanceOfType(inst.Test1.Test, typeof(Test)); + } + + Assert.IsTrue(test1.Test.Disposed); + Assert.IsTrue(test2.Test1.Test.Disposed); + } + } + + public interface ITest : IDisposable { bool Disposed { get; } } public interface ITest1 { ITest Test { get; } } - public class Test : ITest { } + public class Test : ITest + { + public void Dispose() + { + if (this.Disposed) + throw new ObjectDisposedException("disposed"); + + this.Disposed = true; + } + + public bool Disposed { get; private set; } + } public class Test1 : ITest1 { diff --git a/src/stashbox.tests/StandardResolveTests.cs b/src/stashbox.tests/StandardResolveTests.cs index ed00e14a..a8062b5f 100644 --- a/src/stashbox.tests/StandardResolveTests.cs +++ b/src/stashbox.tests/StandardResolveTests.cs @@ -35,6 +35,30 @@ public void StandardResolveTests_Resolve() } } + [TestMethod] + public void StandardResolveTests_Ensure_DependencyResolver_CanBeResolved() + { + using (IStashboxContainer container = new StashboxContainer()) + { + container.RegisterScoped(); + var resolver = container.Resolve(); + + var test = container.Resolve(); + + Assert.AreSame(container, resolver); + Assert.AreSame(container, test.DependencyResolver); + + using (var scope = container.BeginScope()) + { + var scopedResolver = scope.Resolve(); + var test1 = scope.Resolve(); + + Assert.AreSame(scope, scopedResolver); + Assert.AreSame(scope, test1.DependencyResolver); + } + } + } + [TestMethod] public void StandardResolveTests_Factory() { @@ -515,5 +539,15 @@ public Test5(ITest1 test) { } } + + public class ResolverTest + { + public IDependencyResolver DependencyResolver { get; } + + public ResolverTest(IDependencyResolver dependencyResolver) + { + this.DependencyResolver = dependencyResolver; + } + } } } \ No newline at end of file diff --git a/src/stashbox/Infrastructure/IDependencyResolver.cs b/src/stashbox/Infrastructure/IDependencyResolver.cs index 51df3247..265b53ed 100644 --- a/src/stashbox/Infrastructure/IDependencyResolver.cs +++ b/src/stashbox/Infrastructure/IDependencyResolver.cs @@ -144,5 +144,13 @@ public interface IDependencyResolver : IDisposable /// If it's set to true the container will exclude the instance from the disposal tracking. /// The scope. IDependencyResolver PutInstanceInScope(Type typeFrom, object instance, bool withoutDisposalTracking = false); + + /// + /// Builds up an instance, the container will perform injections and extensions on it. + /// + /// The type of the requested instance. + /// The instance to build up. + /// The built object. + TTo BuildUp(TTo instance); } } diff --git a/src/stashbox/Infrastructure/IStashboxContainer.cs b/src/stashbox/Infrastructure/IStashboxContainer.cs index 5bb07e86..47ac7b58 100644 --- a/src/stashbox/Infrastructure/IStashboxContainer.cs +++ b/src/stashbox/Infrastructure/IStashboxContainer.cs @@ -58,14 +58,6 @@ public interface IStashboxContainer : IDependencyRegistrator, IDependencyResolve /// True if the service can be resolved, otherwise false. bool CanResolve(Type typeFrom, object name = null); - /// - /// Builds up an instance, the container will perform injections and extensions on it. - /// - /// The type of the requested instance. - /// The instance to build up. - /// The built object. - TTo BuildUp(TTo instance); - /// /// Configures the container. /// diff --git a/src/stashbox/Resolution/ActivationContext.cs b/src/stashbox/Resolution/ActivationContext.cs index f6cc6e1a..8c8a13ad 100644 --- a/src/stashbox/Resolution/ActivationContext.cs +++ b/src/stashbox/Resolution/ActivationContext.cs @@ -41,6 +41,9 @@ public Delegate ActivateFactory(Type type, Type[] parameterTypes, IResolutionSco private object Activate(ResolutionInfo resolutionInfo, Type type, object name = null) { + if (type == Constants.ResolverType) + return resolutionInfo.ResolutionScope; + if (resolutionInfo.ResolutionScope.HasScopedInstances) { var instance = resolutionInfo.ResolutionScope.GetScopedInstanceOrDefault(type); diff --git a/src/stashbox/Resolution/ResolutionStrategy.cs b/src/stashbox/Resolution/ResolutionStrategy.cs index 3d2ea1ff..f3343c52 100644 --- a/src/stashbox/Resolution/ResolutionStrategy.cs +++ b/src/stashbox/Resolution/ResolutionStrategy.cs @@ -18,6 +18,9 @@ internal ResolutionStrategy(IResolverSelector resolverSelector) public Expression BuildResolutionExpression(IContainerContext containerContext, ResolutionInfo resolutionInfo, TypeInformation typeInformation, InjectionParameter[] injectionParameters) { + if (typeInformation.Type == Constants.ResolverType) + return Expression.Convert(Constants.ScopeExpression, Constants.ResolverType); + if (resolutionInfo.ParameterExpressions != null && resolutionInfo.ParameterExpressions.Any(p => p.Type == typeInformation.Type)) return resolutionInfo.ParameterExpressions.Last(p => p.Type == typeInformation.Type); @@ -32,7 +35,7 @@ public Expression BuildResolutionExpression(IContainerContext containerContext, if (resolutionInfo.ResolutionScope.HasScopedInstances) { var instance = resolutionInfo.ResolutionScope.GetScopedInstanceOrDefault(typeInformation.Type); - if(instance != null) + if (instance != null) return Expression.Constant(instance); } diff --git a/src/stashbox/ResolutionScope.cs b/src/stashbox/ResolutionScope.cs index b7ccd834..5e42fd00 100644 --- a/src/stashbox/ResolutionScope.cs +++ b/src/stashbox/ResolutionScope.cs @@ -1,83 +1,73 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; +using Stashbox.BuildUp.Expressions; +using Stashbox.Entity; using Stashbox.Infrastructure; +using Stashbox.Infrastructure.Registration; using Stashbox.Infrastructure.Resolution; using Stashbox.Utils; namespace Stashbox { - /// - /// Represents a resolution scope. - /// - public class ResolutionScope : ResolutionScopeBase, IDependencyResolver + internal class ResolutionScope : ResolutionScopeBase, IDependencyResolver { private readonly IActivationContext activationContext; - - /// - /// Constructs a resolution scope. - /// - /// The dependency resolver. - public ResolutionScope(IActivationContext activationContext) + private readonly IServiceRegistrator serviceRegistrator; + private readonly IExpressionBuilder expressionBuilder; + private readonly IResolutionScope rootScope; + + public ResolutionScope(IActivationContext activationContext, IServiceRegistrator serviceRegistrator, + IExpressionBuilder expressionBuilder, IResolutionScope rootScope) { this.activationContext = activationContext; + this.serviceRegistrator = serviceRegistrator; + this.expressionBuilder = expressionBuilder; + this.rootScope = rootScope; } - - /// + public TKey Resolve(bool nullResultAllowed = false) => (TKey)this.activationContext.Activate(typeof(TKey), this, nullResultAllowed); - - /// + public object Resolve(Type typeFrom, bool nullResultAllowed = false) => this.activationContext.Activate(typeFrom, this, nullResultAllowed); - - /// + public TKey Resolve(object name, bool nullResultAllowed = false) => (TKey)this.activationContext.Activate(typeof(TKey), this, name, nullResultAllowed); - - /// + public object Resolve(Type typeFrom, object name, bool nullResultAllowed = false) => this.activationContext.Activate(typeFrom, this, name, nullResultAllowed); - - /// + public IEnumerable ResolveAll() => (IEnumerable)this.activationContext.Activate(typeof(IEnumerable), this); - - /// + public IEnumerable ResolveAll(Type typeFrom) => (IEnumerable)this.activationContext.Activate(typeof(IEnumerable<>).MakeGenericType(typeFrom), this); - - /// + public Delegate ResolveFactory(Type typeFrom, object name = null, bool nullResultAllowed = false, params Type[] parameterTypes) => this.activationContext.ActivateFactory(typeFrom, parameterTypes, this, name, nullResultAllowed); - - /// + public Func ResolveFactory(object name = null, bool nullResultAllowed = false) => this.ResolveFactory(typeof(TService), name, nullResultAllowed) as Func; - - /// + public Func ResolveFactory(object name = null, bool nullResultAllowed = false) => this.ResolveFactory(typeof(TService), name, nullResultAllowed, typeof(T1)) as Func; - - /// + public Func ResolveFactory(object name = null, bool nullResultAllowed = false) => this.ResolveFactory(typeof(TService), name, nullResultAllowed, typeof(T1), typeof(T2)) as Func; - - /// + public Func ResolveFactory(object name = null, bool nullResultAllowed = false) => this.ResolveFactory(typeof(TService), name, nullResultAllowed, typeof(T1), typeof(T2), typeof(T3)) as Func; - - /// + public Func ResolveFactory(object name = null, bool nullResultAllowed = false) => this.ResolveFactory(typeof(TService), name, nullResultAllowed, typeof(T1), typeof(T2), typeof(T3), typeof(T4)) as Func; - - /// - public IDependencyResolver BeginScope() => new ResolutionScope(this.activationContext); - - /// + + public IDependencyResolver BeginScope() => new ResolutionScope(this.activationContext, this.serviceRegistrator, + this.expressionBuilder, this.rootScope); + public IDependencyResolver PutInstanceInScope(TFrom instance, bool withoutDisposalTracking = false) => this.PutInstanceInScope(typeof(TFrom), instance, withoutDisposalTracking); - - /// + public IDependencyResolver PutInstanceInScope(Type typeFrom, object instance, bool withoutDisposalTracking = false) { Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); @@ -89,5 +79,16 @@ public IDependencyResolver PutInstanceInScope(Type typeFrom, object instance, bo return this; } + + /// + public TTo BuildUp(TTo instance) + { + var typeTo = instance.GetType(); + var registration = this.serviceRegistrator.PrepareContext(typeTo, typeTo); + var expr = this.expressionBuilder.CreateFillExpression(registration.CreateServiceRegistration(false), + Expression.Constant(instance), ResolutionInfo.New(this, this.rootScope), typeTo); + var factory = expr.CompileDelegate(Constants.ScopeExpression); + return (TTo)factory(this); + } } } diff --git a/src/stashbox/StashboxContainer.cs b/src/stashbox/StashboxContainer.cs index 0023e7ce..ef6e6225 100644 --- a/src/stashbox/StashboxContainer.cs +++ b/src/stashbox/StashboxContainer.cs @@ -109,7 +109,8 @@ public IStashboxContainer CreateChildContainer() => new StashboxContainer(this, this.containerExtensionManager.CreateCopy(), this.resolverSelector); /// - public IDependencyResolver BeginScope() => new ResolutionScope(this.activationContext); + public IDependencyResolver BeginScope() => new ResolutionScope(this.activationContext, + this.ServiceRegistrator, this.expressionBuilder, this); /// public void Configure(Action config) =>