From 3618e01d7ff0510dceed54b395f4721eb0abb84b Mon Sep 17 00:00:00 2001 From: dotnetjunkie Date: Thu, 21 Mar 2019 16:47:41 +0100 Subject: [PATCH 1/2] Type in XML docs fixed. --- src/SimpleInjector/VerificationOption.enum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleInjector/VerificationOption.enum.cs b/src/SimpleInjector/VerificationOption.enum.cs index eb54ca56a..6e6abd01c 100644 --- a/src/SimpleInjector/VerificationOption.enum.cs +++ b/src/SimpleInjector/VerificationOption.enum.cs @@ -23,7 +23,7 @@ namespace SimpleInjector { /// - /// This enumeration has defines in which way the container should run the verification process. + /// This enumeration defines in which way the container should run the verification process. /// public enum VerificationOption { From cf8c9ce23949a7c819d52faf0f89179379561245 Mon Sep 17 00:00:00 2001 From: dotnetjunkie Date: Thu, 21 Mar 2019 18:34:04 +0100 Subject: [PATCH 2/2] Container.Options.ResolveUnregisteredConcreteTypes setting added to disable creation of unregistered concrete types. Fixes #377. --- .../ContainerOptionsTests.cs | 32 +++++ .../ResolveUnregisteredTypeEventTests.cs | 121 +++++++++++++++++- src/SimpleInjector/Container.Common.cs | 1 + src/SimpleInjector/Container.Resolving.cs | 30 ++--- src/SimpleInjector/ContainerOptions.cs | 30 ++++- src/SimpleInjector/StringResources.cs | 33 ++++- 6 files changed, 225 insertions(+), 22 deletions(-) diff --git a/src/SimpleInjector.Tests.Unit/ContainerOptionsTests.cs b/src/SimpleInjector.Tests.Unit/ContainerOptionsTests.cs index 599794ac2..3864fe4c1 100644 --- a/src/SimpleInjector.Tests.Unit/ContainerOptionsTests.cs +++ b/src/SimpleInjector.Tests.Unit/ContainerOptionsTests.cs @@ -936,6 +936,38 @@ public void LifestyleSelectionBehavior_DefaultImplementation_RedirectsToDefaultL // Assert Assert.AreSame(Lifestyle.Singleton, lifestyle); } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_ByDefault_True() + { + // Arrange + var expectedValue = true; + + var container = new Container(); + + // Act + var actualValue = container.Options.ResolveUnregisteredConcreteTypes; + + // Assert + Assert.AreEqual(expectedValue, actualValue); + } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_ChangedAfterContainerIsLocked_ThrowsAnException() + { + // Arrange + var container = new Container(); + + container.GetInstance(); + + // Act + Action action = () => container.Options.ResolveUnregisteredConcreteTypes = false; + + // Assert + AssertThat.ThrowsWithExceptionMessageContains( + "The container can't be changed after the first call", + action); + } private static PropertyInfo GetProperty(Expression> propertySelector) { diff --git a/src/SimpleInjector.Tests.Unit/ResolveUnregisteredTypeEventTests.cs b/src/SimpleInjector.Tests.Unit/ResolveUnregisteredTypeEventTests.cs index 50ca62fea..db457f6de 100644 --- a/src/SimpleInjector.Tests.Unit/ResolveUnregisteredTypeEventTests.cs +++ b/src/SimpleInjector.Tests.Unit/ResolveUnregisteredTypeEventTests.cs @@ -290,7 +290,7 @@ public void GetInstance_EventRegisteredThatThrowsException_ThrowsAnDescriptiveEx { e.Register(() => { throw new Exception(); }); }; - + // Act Action action = () => container.GetInstance(); @@ -611,6 +611,125 @@ public void ResolveUnregisteredType_Always_IsExpectedToBeCached() Assert.AreEqual(1, callCount, "The result of ResolveUnregisteredType is expected to be cached."); } + [TestMethod] + public void ResolveUnregisteredConcreteTypes_SetToFalse_DoesNotAllowUnregisteredConcreteRootTypesToBeResolved() + { + // Arrange + var container = new Container(); + container.Options.ResolveUnregisteredConcreteTypes = false; + + // Add a dummy registration. + container.RegisterInstance(new FakeTimeProvider()); + + // Act + Action action = () => container.GetInstance(); + + // Assert + AssertThat.ThrowsWithExceptionMessageContains(@" + No registration for type ConcreteCommand could be found and an implicit registration + could not be made. Note that the container's Options.ResolveUnregisteredConcreteTypes + option is set to 'false'. This disallows the container to construct this unregistered + concrete type." + .TrimInside(), + action); + } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_SetToFalse_DoesNotAllowUnregisteredConcreteDependenciesToBeResolved() + { + // Arrange + var container = new Container(); + container.Options.ResolveUnregisteredConcreteTypes = false; + + container.Register>(); + + // Act + Action action = () => container.GetInstance>(); + + // Assert + AssertThat.ThrowsWithExceptionMessageContains(@" + The constructor of type ServiceDependingOn contains + the parameter with name 'dependency' and type ConcreteCommand that is not + registered. Please ensure ConcreteCommand is registered, or change the constructor of + ServiceDependingOn. Note that the container's + Options.ResolveUnregisteredConcreteTypes option is set to 'false'. This disallows the + container to construct this unregistered concrete type." + .TrimInside(), + action); + } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_SetToNever_DoesAllowRegisteredConcreteRootTypesToBeResolved() + { + // Arrange + var container = new Container(); + container.Options.ResolveUnregisteredConcreteTypes = false; + + // Add a dummy registration. + container.Register(); + + // Act + container.GetInstance(); + } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_SetToNever_DoesAllowRegisteredConcreteDependenciesToBeResolved() + { + // Arrange + var container = new Container(); + container.Options.ResolveUnregisteredConcreteTypes = false; + + container.Register(); + container.Register>(); + + // Act + container.GetInstance>(); + } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_SetToTrue_DoesAllowUnregisteredConcreteRootTypesToBeResolved() + { + // Arrange + var container = new Container(); + container.Options.ResolveUnregisteredConcreteTypes = true; + + // Act + container.GetInstance(); + } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_SetToTrue_DoesAllowRegisteredConcreteDependenciesToBeResolved() + { + // Arrange + var container = new Container(); + container.Options.ResolveUnregisteredConcreteTypes = true; + + // Act + container.GetInstance>(); + } + + [TestMethod] + public void ResolveUnregisteredConcreteTypes_SetToFalseWithUnregisteredTypeHandlingType_DoesAllowUnregisteredConcreteDependenciesToBeResolved() + { + // Arrange + var container = new Container(); + container.Options.ResolveUnregisteredConcreteTypes = false; + + // Using unregistered type resolution re-enable the 'Always' behavior. + container.ResolveUnregisteredType += (s, e) => + { + if (!e.Handled && !e.UnregisteredServiceType.IsAbstract) + { + e.Register(container.Options.LifestyleSelectionBehavior + .SelectLifestyle(e.UnregisteredServiceType) + .CreateRegistration(e.UnregisteredServiceType, container)); + } + }; + + // Act + container.GetInstance>(); + } + public class CompositeService where T : struct { public CompositeService(Nullable[] dependencies) diff --git a/src/SimpleInjector/Container.Common.cs b/src/SimpleInjector/Container.Common.cs index 8e99209f4..4dbf1615c 100644 --- a/src/SimpleInjector/Container.Common.cs +++ b/src/SimpleInjector/Container.Common.cs @@ -478,6 +478,7 @@ internal void ThrowParameterTypeMustBeRegistered(InjectionTargetInfo target) { throw new ActivationException( StringResources.ParameterTypeMustBeRegistered( + this, target, this.GetNumberOfConditionalRegistrationsFor(target.TargetType), this.ContainsOneToOneRegistrationForCollectionType(target.TargetType), diff --git a/src/SimpleInjector/Container.Resolving.cs b/src/SimpleInjector/Container.Resolving.cs index 0f72cd072..3849c5d72 100644 --- a/src/SimpleInjector/Container.Resolving.cs +++ b/src/SimpleInjector/Container.Resolving.cs @@ -270,6 +270,9 @@ internal InstanceProducer GetRegistrationEvenIfInvalid(Type serviceType, Injecti return this.GetInstanceProducerForType(serviceType, consumer, buildProducer); } + internal bool IsConcreteConstructableType(Type concreteType) => + this.Options.IsConstructableType(concreteType, out string errorMesssage); + private Action GetInitializer(Type implementationType, Registration context) { Action[] initializersForType = this.GetInstanceInitializersFor(implementationType, context); @@ -613,7 +616,8 @@ private InstanceProducer TryBuildInstanceProducerForConcreteUnregisteredType { @@ -630,8 +634,11 @@ private InstanceProducer TryBuildInstanceProducerForConcreteUnregisteredType private void ThrowNotConstructableException(Type concreteType) { - string exceptionMessage; - - // Since we are at this point, we know the concreteType is NOT constructable. - this.Options.IsConstructableType(concreteType, out exceptionMessage); + // At this point we know the concreteType is either NOT constructable or + // Options.ResolveUnregisteredConcreteTypes is configured to not return the type. + this.Options.IsConstructableType(concreteType, out string exceptionMessage); throw new ActivationException( - StringResources.ImplicitRegistrationCouldNotBeMadeForType(concreteType, this.HasRegistrations) + StringResources.ImplicitRegistrationCouldNotBeMadeForType( + this, concreteType, this.HasRegistrations) + " " + exceptionMessage); } } diff --git a/src/SimpleInjector/ContainerOptions.cs b/src/SimpleInjector/ContainerOptions.cs index ea01a5ce4..3c377d855 100644 --- a/src/SimpleInjector/ContainerOptions.cs +++ b/src/SimpleInjector/ContainerOptions.cs @@ -1,7 +1,7 @@ #region Copyright Simple Injector Contributors /* The Simple Injector is an easy-to-use Inversion of Control library for .NET * - * Copyright (c) 2013-2015 Simple Injector Contributors + * Copyright (c) 2013-2019 Simple Injector Contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -26,7 +26,6 @@ namespace SimpleInjector using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using SimpleInjector.Advanced; @@ -77,6 +76,9 @@ public class ContainerOptions [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ScopedLifestyle defaultScopedLifestyle; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private bool resolveUnregisteredConcreteTypes = true; + internal ContainerOptions(Container container) { Requires.IsNotNull(container, nameof(container)); @@ -132,6 +134,30 @@ public bool UseFullyQualifiedTypeNames set { StringResources.UseFullyQualifiedTypeNames = value; } } + /// + /// Gets or sets a value indicating whether the container should resolve unregistered concrete types. + /// The default value is true. Consider changing the value to false to prevent + /// accidental creation of types you haven't registered explicitly. + /// + /// The value indicating whether the container should resolve unregistered concrete types. + /// + /// Thrown when this container instance is locked and can not be altered. + /// + public bool ResolveUnregisteredConcreteTypes + { + get + { + return this.resolveUnregisteredConcreteTypes; + } + + set + { + this.Container.ThrowWhenContainerIsLockedOrDisposed(); + + this.resolveUnregisteredConcreteTypes = value; + } + } + /// /// Gets or sets the constructor resolution behavior. By default, the container only supports types /// that have a single public constructor. diff --git a/src/SimpleInjector/StringResources.cs b/src/SimpleInjector/StringResources.cs index 6bddf4a86..58a16b90c 100644 --- a/src/SimpleInjector/StringResources.cs +++ b/src/SimpleInjector/StringResources.cs @@ -242,13 +242,18 @@ internal static string CollectionTypeAlreadyRegistered(Type serviceType) => nameof(Container.Collection), nameof(ContainerCollectionRegistrator.Append)); - internal static string ParameterTypeMustBeRegistered(InjectionTargetInfo target, int numberOfConditionals, - bool hasRelatedOneToOneMapping, bool hasRelatedCollectionMapping, Type[] skippedDecorators, + internal static string ParameterTypeMustBeRegistered( + Container container, + InjectionTargetInfo target, + int numberOfConditionals, + bool hasRelatedOneToOneMapping, + bool hasRelatedCollectionMapping, + Type[] skippedDecorators, Type[] lookalikes) => target.Parameter != null ? string.Format(CultureInfo.InvariantCulture, "The constructor of type {0} contains the parameter with name '{1}' and type {2} that " + - "is not registered. Please ensure {2} is registered, or change the constructor of {0}.{3}{4}{5}{6}{7}", + "is not registered. Please ensure {2} is registered, or change the constructor of {0}.{3}{4}{5}{6}{7}{8}", target.Member.DeclaringType.TypeName(), target.Name, target.TargetType.TypeName(), @@ -256,10 +261,11 @@ internal static string ParameterTypeMustBeRegistered(InjectionTargetInfo target, DidYouMeanToDependOnNonCollectionInstead(hasRelatedOneToOneMapping, target.TargetType), DidYouMeanToDependOnCollectionInstead(hasRelatedCollectionMapping, target.TargetType), NoteThatSkippedDecoratorsWereFound(target.TargetType, skippedDecorators), + NoteThatConcreteTypeCanNotBeResolvedDueToConfiguration(container, target.TargetType), NoteThatTypeLookalikesAreFound(target.TargetType, lookalikes, numberOfConditionals)) : string.Format(CultureInfo.InvariantCulture, "Type {0} contains the property with name '{1}' and type {2} that is not registered. " + - "Please ensure {2} is registered, or change {0}.{3}{4}{5}{6}{7}", + "Please ensure {2} is registered, or change {0}.{3}{4}{5}{6}{7}{8}", target.Member.DeclaringType.TypeName(), target.Name, target.TargetType.TypeName(), @@ -267,6 +273,7 @@ internal static string ParameterTypeMustBeRegistered(InjectionTargetInfo target, DidYouMeanToDependOnNonCollectionInstead(hasRelatedOneToOneMapping, target.TargetType), DidYouMeanToDependOnCollectionInstead(hasRelatedCollectionMapping, target.TargetType), NoteThatSkippedDecoratorsWereFound(target.TargetType, skippedDecorators), + NoteThatConcreteTypeCanNotBeResolvedDueToConfiguration(container, target.TargetType), NoteThatTypeLookalikesAreFound(target.TargetType, lookalikes, numberOfConditionals)); internal static string TypeMustHaveASinglePublicConstructorButItHasNone(Type serviceType) => @@ -336,6 +343,16 @@ internal static string ImplicitRegistrationCouldNotBeMadeForType(Type serviceTyp serviceType.TypeName(), ContainerHasNoRegistrationsAddition(containerHasRegistrations)); + internal static string ImplicitRegistrationCouldNotBeMadeForType( + Container container, + Type serviceType, + bool containerHasRegistrations) => + string.Format(CultureInfo.InvariantCulture, + "No registration for type {0} could be found and an implicit registration could not be made.{1}{2}", + serviceType.TypeName(), + NoteThatConcreteTypeCanNotBeResolvedDueToConfiguration(container, serviceType), + ContainerHasNoRegistrationsAddition(containerHasRegistrations)); + internal static string DefaultScopedLifestyleCanNotBeSetWithLifetimeScoped() => string.Format(CultureInfo.InvariantCulture, "{0} can't be set with the value of {1}.{2}.", @@ -975,6 +992,14 @@ private static string NoteThatTypeLookalikesAreFound(Type serviceType, Type[] lo } } + private static string NoteThatConcreteTypeCanNotBeResolvedDueToConfiguration( + Container container, Type serviceType) => + container.IsConcreteConstructableType(serviceType) + && !container.Options.ResolveUnregisteredConcreteTypes + ? " Note that the container's Options.ResolveUnregisteredConcreteTypes option is set " + + "to 'false'. This disallows the container to construct this unregistered concrete type." + : string.Empty; + private static string BuildAssemblyLocationMessage(Type serviceType, Type duplicateAssemblyLookalike) { string serviceTypeLocation = GetAssemblyLocationOrNull(serviceType);