From 02486c77b8426c4167a99be7c5f7243990877b0d Mon Sep 17 00:00:00 2001 From: vurt007 Date: Tue, 16 Apr 2013 11:27:13 +0100 Subject: [PATCH 1/2] added support for multi-instance, singleton and custom lifetime delegate factories - also made http lifetime provider not bomb out in unit tests --- src/TinyIoC.Tests/TinyIoCTests.cs | 25 ++-- src/TinyIoC/TinyIoC.cs | 171 ++++++++++++++++++++++++- src/TinyIoC/TinyIoCAspNetExtensions.cs | 8 +- 3 files changed, 184 insertions(+), 20 deletions(-) diff --git a/src/TinyIoC.Tests/TinyIoCTests.cs b/src/TinyIoC.Tests/TinyIoCTests.cs index 001638a..25ba663 100644 --- a/src/TinyIoC.Tests/TinyIoCTests.cs +++ b/src/TinyIoC.Tests/TinyIoCTests.cs @@ -1031,25 +1031,28 @@ public void Register_SingletonWithWeakReference_Throws() } [TestMethod] - //[ExpectedException(typeof(TinyIoCRegistrationException))] - public void Register_FactoryToSingletonFluent_ThrowsException() + public void Register_FactoryToSingletonFluent_Registers() { var container = UtilityMethods.GetContainer(); - AssertHelper.ThrowsException(() => container.Register((c, p) => new TestClassDefaultCtor()).AsSingleton()); - - // Should have thrown by now - //Assert.IsTrue(false); + container.Register((c, p) => new TestClassDefaultCtor()).AsSingleton(); + //works } [TestMethod] - //[ExpectedException(typeof(TinyIoCRegistrationException))] - public void Register_FactoryToMultiInstanceFluent_ThrowsException() + public void Register_FactoryToMultiInstanceFluent_Registers() { var container = UtilityMethods.GetContainer(); - AssertHelper.ThrowsException(() => container.Register((c, p) => new TestClassDefaultCtor()).AsMultiInstance()); + container.Register((c, p) => new TestClassDefaultCtor()).AsMultiInstance(); - // Should have thrown by now - //Assert.IsTrue(false); + //works + } + + [TestMethod] + public void Register_FactoryToCustomLifetime_Registers() + { + var container = UtilityMethods.GetContainer(); + container.Register((c, p) => new TestClassDefaultCtor()).AsPerRequestSingleton(); + //works } [TestMethod] diff --git a/src/TinyIoC/TinyIoC.cs b/src/TinyIoC/TinyIoC.cs index 35d28fb..0153528 100644 --- a/src/TinyIoC/TinyIoC.cs +++ b/src/TinyIoC/TinyIoC.cs @@ -2515,14 +2515,139 @@ public override ObjectFactoryBase MultiInstanceVariant } } + + /// + /// A factory that offloads lifetime to an external lifetime provider + /// + private class DelegateCustomObjectLifetimeFactory : DelegateFactory, IDisposable + { + private readonly object SingletonLock = new object(); + private readonly ITinyIoCObjectLifetimeProvider _LifetimeProvider; + + public DelegateCustomObjectLifetimeFactory(Type registerType, Func factory, ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorMessage) + : base(registerType, factory) + { + if (lifetimeProvider == null) + throw new ArgumentNullException("lifetimeProvider", "lifetimeProvider is null."); + + _LifetimeProvider = lifetimeProvider; + } + + public override Type CreatesType + { + get { return registerType; } + } + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + object current; + + lock (SingletonLock) + { + current = _LifetimeProvider.GetObject(); + if (current == null) + { + current = base.GetObject(requestedType, container, parameters, options); + _LifetimeProvider.SetObject(current); + } + } + + return current; + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + _LifetimeProvider.ReleaseObject(); + return new SingletonDelegateFactory(registerType, _factory); + } + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + _LifetimeProvider.ReleaseObject(); + return new DelegateFactory(registerType, _factory); + } + } + + public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + _LifetimeProvider.ReleaseObject(); + return new DelegateCustomObjectLifetimeFactory(registerType, _factory, lifetimeProvider, errorString); + } + + public override ObjectFactoryBase GetFactoryForChildContainer(Type type, TinyIoCContainer parent, TinyIoCContainer child) + { + // We make sure that the singleton is constructed before the child container takes the factory. + // Otherwise the results would vary depending on whether or not the parent container had resolved + // the type before the child container does. + GetObject(type, parent, NamedParameterOverloads.Default, ResolveOptions.Default); + return this; + } + + public void Dispose() + { + _LifetimeProvider.ReleaseObject(); + } + } + + private class SingletonDelegateFactory : DelegateFactory + { + public SingletonDelegateFactory(Type registerType, Func factory) + : base(registerType, factory) + { + } + + private readonly object SingletonLock = new object(); + private object _Current; + + + public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) + { + if (parameters.Count != 0) + throw new ArgumentException("Cannot specify parameters for singleton types"); + + lock (SingletonLock) + if (_Current == null) + _Current = _factory.Invoke(container, parameters); + + return _Current; + } + + public override ObjectFactoryBase SingletonVariant + { + get + { + return this; + } + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get + { + return new DelegateFactory(registerType, _factory); + } + } + + public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + return new DelegateCustomObjectLifetimeFactory(registerType, _factory, lifetimeProvider, errorString); + } + } + + /// /// IObjectFactory that invokes a specified delegate to construct the object /// private class DelegateFactory : ObjectFactoryBase { - private readonly Type registerType; + protected readonly Type registerType; - private Func _factory; + protected readonly Func _factory; public override bool AssumeConstruction { get { return true; } } @@ -2550,14 +2675,32 @@ public DelegateFactory( Type registerType, Func Target {get { return _factory.Target as Func; }} + public override bool AssumeConstruction { get { return true; } } public override Type CreatesType { get { return this.registerType; } } public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) { - var factory = _factory.Target as Func; + var factory = Target; if (factory == null) throw new TinyIoCWeakReferenceException(this.registerType); @@ -2634,6 +2779,24 @@ public override ObjectFactoryBase WeakReferenceVariant } } + public override ObjectFactoryBase SingletonVariant + { + get + { + return new SingletonDelegateFactory(registerType, Target); + } + } + + public override ObjectFactoryBase MultiInstanceVariant + { + get { return this; } + } + + public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) + { + return new DelegateCustomObjectLifetimeFactory(registerType, Target, lifetimeProvider, errorString); + } + public override void SetConstructor(ConstructorInfo constructor) { throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for delegate factory registrations"); diff --git a/src/TinyIoC/TinyIoCAspNetExtensions.cs b/src/TinyIoC/TinyIoCAspNetExtensions.cs index 1838334..6783363 100644 --- a/src/TinyIoC/TinyIoCAspNetExtensions.cs +++ b/src/TinyIoC/TinyIoCAspNetExtensions.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web; namespace TinyIoC @@ -12,12 +9,13 @@ public class HttpContextLifetimeProvider : TinyIoCContainer.ITinyIoCObjectLifeti public object GetObject() { - return HttpContext.Current.Items[_KeyName]; + return HttpContext.Current == null ? null : HttpContext.Current.Items[_KeyName]; } public void SetObject(object value) { - HttpContext.Current.Items[_KeyName] = value; + if (HttpContext.Current != null) + HttpContext.Current.Items[_KeyName] = value; } public void ReleaseObject() From b38e118b3d99cfbad6cf956a9718a07ebe759722 Mon Sep 17 00:00:00 2001 From: vurt007 Date: Thu, 18 Apr 2013 10:47:31 +0100 Subject: [PATCH 2/2] cyclic dependency checking --- src/TinyIoC.Tests/TestData/BasicClasses.cs | 21 ++ src/TinyIoC.Tests/TinyIoCFunctionalTests.cs | 25 ++ src/TinyIoC/TinyIoC.cs | 239 ++++++++++++-------- 3 files changed, 187 insertions(+), 98 deletions(-) diff --git a/src/TinyIoC.Tests/TestData/BasicClasses.cs b/src/TinyIoC.Tests/TestData/BasicClasses.cs index bb7c820..bfd51e3 100644 --- a/src/TinyIoC.Tests/TestData/BasicClasses.cs +++ b/src/TinyIoC.Tests/TestData/BasicClasses.cs @@ -22,6 +22,27 @@ namespace TinyIoC.Tests.TestData { namespace BasicClasses { + + internal class CyclicA + { + readonly CyclicB dependency; + + public CyclicA(CyclicB dependency) + { + this.dependency = dependency; + } + } + + internal class CyclicB + { + readonly CyclicA dependency; + + public CyclicB(CyclicA dependency) + { + this.dependency = dependency; + } + } + internal interface ITestInterface { } diff --git a/src/TinyIoC.Tests/TinyIoCFunctionalTests.cs b/src/TinyIoC.Tests/TinyIoCFunctionalTests.cs index 1cfe52b..e851163 100644 --- a/src/TinyIoC.Tests/TinyIoCFunctionalTests.cs +++ b/src/TinyIoC.Tests/TinyIoCFunctionalTests.cs @@ -38,6 +38,31 @@ namespace TinyIoC.Tests [TestClass] public class TinyIoCFunctionalTests { + + [TestMethod] + public void Container_Throws_Whith_Simple_Cyclic_Dependency() + { + var container = UtilityMethods.GetContainer(); + container.Register(); + container.Register(); + + AssertHelper.ThrowsException(() => container.Resolve()); + + } + + + [TestMethod] + public void Container_Throws_Whith_Simple_Factory_Cyclic_Dependency() + { + var container = UtilityMethods.GetContainer(); + container.Register((c, o) => new CyclicA(c.Resolve())); + container.Register((c, o) => new CyclicB(c.Resolve())); + + AssertHelper.ThrowsException(() => container.Resolve()); + + } + + [TestMethod] public void NestedInterfaceDependencies_CorrectlyRegistered_ResolvesRoot() { diff --git a/src/TinyIoC/TinyIoC.cs b/src/TinyIoC/TinyIoC.cs index 0153528..f46b3b8 100644 --- a/src/TinyIoC/TinyIoC.cs +++ b/src/TinyIoC/TinyIoC.cs @@ -3370,56 +3370,69 @@ private bool CanResolveInternal(TypeRegistration registration, NamedParameterOve if (parameters == null) throw new ArgumentNullException("parameters"); - Type checkType = registration.Type; - string name = registration.Name; + var cyclicList = DotNet35ThreadLocalCyclicCheck(); + if(cyclicList.Contains(registration.Type)) + throw new InvalidOperationException("Cyclic dependency detected"); - ObjectFactoryBase factory; - if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType, name), out factory)) + try { - if (factory.AssumeConstruction) - return true; + cyclicList.Add(registration.Type); - if (factory.Constructor == null) - return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; - else - return CanConstruct(factory.Constructor, parameters, options); - } - - // Fail if requesting named resolution and settings set to fail if unresolved - // Or bubble up if we have a parent - if (!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) - return (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; + Type checkType = registration.Type; + string name = registration.Name; - // Attemped unnamed fallback container resolution if relevant and requested - if (!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) - { - if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType), out factory)) + ObjectFactoryBase factory; + if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType, name), out factory)) { if (factory.AssumeConstruction) return true; - return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + if (factory.Constructor == null) + return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + else + return CanConstruct(factory.Constructor, parameters, options); } - } - // Check if type is an automatic lazy factory request - if (IsAutomaticLazyFactoryRequest(checkType)) - return true; + // Fail if requesting named resolution and settings set to fail if unresolved + // Or bubble up if we have a parent + if (!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) + return (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; - // Check if type is an IEnumerable - if (IsIEnumerableRequest(registration.Type)) - return true; + // Attemped unnamed fallback container resolution if relevant and requested + if (!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) + { + if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType), out factory)) + { + if (factory.AssumeConstruction) + return true; - // Attempt unregistered construction if possible and requested - // If we cant', bubble if we have a parent - if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (checkType.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) - return (GetBestConstructor(checkType, parameters, options) != null) ? true : (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; + return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; + } + } - // Bubble resolution up the container tree if we have a parent - if (_Parent != null) - return _Parent.CanResolveInternal(registration, parameters, options); + // Check if type is an automatic lazy factory request + if (IsAutomaticLazyFactoryRequest(checkType)) + return true; - return false; + // Check if type is an IEnumerable + if (IsIEnumerableRequest(registration.Type)) + return true; + + // Attempt unregistered construction if possible and requested + // If we cant', bubble if we have a parent + if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (checkType.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) + return (GetBestConstructor(checkType, parameters, options) != null) ? true : (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; + + // Bubble resolution up the container tree if we have a parent + if (_Parent != null) + return _Parent.CanResolveInternal(registration, parameters, options); + + return false; + } + finally + { + cyclicList.Remove(registration.Type); + } } private bool IsIEnumerableRequest(Type type) @@ -3479,35 +3492,37 @@ private ObjectFactoryBase GetParentObjectFactory(TypeRegistration registration) return _Parent.GetParentObjectFactory(registration); } + #region wish_dotnet35_had_threadlock + readonly Guid instanceid = Guid.NewGuid(); + List DotNet35ThreadLocalCyclicCheck() + { + var slot = Thread.GetNamedDataSlot("tinyIoc_" + instanceid); + + var result = Thread.GetData(slot) as List; + if (result != null) + return result; + + result = new List(); + Thread.SetData(slot,result); + return result; + } + #endregion + + private object ResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options) { - ObjectFactoryBase factory; + var cyclicList = DotNet35ThreadLocalCyclicCheck(); + if(cyclicList.Contains(registration.Type)) + throw new InvalidOperationException("Cyclic dependency detected"); - // Attempt container resolution - if (_RegisteredTypes.TryGetValue(registration, out factory)) + try { - try - { - return factory.GetObject(registration.Type, this, parameters, options); - } - catch (TinyIoCResolutionException) - { - throw; - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(registration.Type, ex); - } - } + cyclicList.Add(registration.Type); -#if RESOLVE_OPEN_GENERICS - // Attempt container resolution of open generic - if (registration.Type.IsGenericType()) - { - var openTypeRegistration = new TypeRegistration(registration.Type.GetGenericTypeDefinition(), - registration.Name); + ObjectFactoryBase factory; - if (_RegisteredTypes.TryGetValue(openTypeRegistration, out factory)) + // Attempt container resolution + if (_RegisteredTypes.TryGetValue(registration, out factory)) { try { @@ -3522,39 +3537,39 @@ private object ResolveInternal(TypeRegistration registration, NamedParameterOver throw new TinyIoCResolutionException(registration.Type, ex); } } - } -#endif - // Attempt to get a factory from parent if we can - var bubbledObjectFactory = GetParentObjectFactory(registration); - if (bubbledObjectFactory != null) - { - try - { - return bubbledObjectFactory.GetObject(registration.Type, this, parameters, options); - } - catch (TinyIoCResolutionException) - { - throw; - } - catch (Exception ex) +#if RESOLVE_OPEN_GENERICS + // Attempt container resolution of open generic + if (registration.Type.IsGenericType()) { - throw new TinyIoCResolutionException(registration.Type, ex); - } - } + var openTypeRegistration = new TypeRegistration(registration.Type.GetGenericTypeDefinition(), + registration.Name); - // Fail if requesting named resolution and settings set to fail if unresolved - if (!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) - throw new TinyIoCResolutionException(registration.Type); + if (_RegisteredTypes.TryGetValue(openTypeRegistration, out factory)) + { + try + { + return factory.GetObject(registration.Type, this, parameters, options); + } + catch (TinyIoCResolutionException) + { + throw; + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + } +#endif - // Attemped unnamed fallback container resolution if relevant and requested - if (!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) - { - if (_RegisteredTypes.TryGetValue(new TypeRegistration(registration.Type, string.Empty), out factory)) + // Attempt to get a factory from parent if we can + var bubbledObjectFactory = GetParentObjectFactory(registration); + if (bubbledObjectFactory != null) { try { - return factory.GetObject(registration.Type, this, parameters, options); + return bubbledObjectFactory.GetObject(registration.Type, this, parameters, options); } catch (TinyIoCResolutionException) { @@ -3565,25 +3580,53 @@ private object ResolveInternal(TypeRegistration registration, NamedParameterOver throw new TinyIoCResolutionException(registration.Type, ex); } } - } + + // Fail if requesting named resolution and settings set to fail if unresolved + if (!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) + throw new TinyIoCResolutionException(registration.Type); + + // Attemped unnamed fallback container resolution if relevant and requested + if (!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) + { + if (_RegisteredTypes.TryGetValue(new TypeRegistration(registration.Type, string.Empty), out factory)) + { + try + { + return factory.GetObject(registration.Type, this, parameters, options); + } + catch (TinyIoCResolutionException) + { + throw; + } + catch (Exception ex) + { + throw new TinyIoCResolutionException(registration.Type, ex); + } + } + } #if EXPRESSIONS - // Attempt to construct an automatic lazy factory if possible - if (IsAutomaticLazyFactoryRequest(registration.Type)) - return GetLazyAutomaticFactoryRequest(registration.Type); + // Attempt to construct an automatic lazy factory if possible + if (IsAutomaticLazyFactoryRequest(registration.Type)) + return GetLazyAutomaticFactoryRequest(registration.Type); #endif - if (IsIEnumerableRequest(registration.Type)) - return GetIEnumerableRequest(registration.Type); + if (IsIEnumerableRequest(registration.Type)) + return GetIEnumerableRequest(registration.Type); - // Attempt unregistered construction if possible and requested - if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (registration.Type.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) + // Attempt unregistered construction if possible and requested + if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (registration.Type.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) + { + if (!registration.Type.IsAbstract() && !registration.Type.IsInterface()) + return ConstructType(null, registration.Type, parameters, options); + } + + // Unable to resolve - throw + throw new TinyIoCResolutionException(registration.Type); + } + finally { - if (!registration.Type.IsAbstract() && !registration.Type.IsInterface()) - return ConstructType(null, registration.Type, parameters, options); + cyclicList.Remove(registration.Type); } - - // Unable to resolve - throw - throw new TinyIoCResolutionException(registration.Type); } #if EXPRESSIONS