Skip to content
Permalink
Browse files

Merge branch 'feature-377' of https://github.com/simpleinjector/Simpl…

…eInjector into v4.5.x
  • Loading branch information...
dotnetjunkie committed Mar 21, 2019
2 parents 29ed6eb + cf8c9ce commit f985eb554a189b6ea50b78afafa5a80123d2c41e
@@ -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<ConcreteCommand>();

// Act
Action action = () => container.Options.ResolveUnregisteredConcreteTypes = false;

// Assert
AssertThat.ThrowsWithExceptionMessageContains<InvalidOperationException>(
"The container can't be changed after the first call",
action);
}

private static PropertyInfo GetProperty<T>(Expression<Func<T, object>> propertySelector)
{
@@ -290,7 +290,7 @@ public void GetInstance_EventRegisteredThatThrowsException_ThrowsAnDescriptiveEx
{
e.Register(() => { throw new Exception(); });
};

// Act
Action action = () => container.GetInstance<IUserRepository>();

@@ -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<ConcreteCommand>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(@"
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<ServiceDependingOn<ConcreteCommand>>();

// Act
Action action = () => container.GetInstance<ServiceDependingOn<ConcreteCommand>>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(@"
The constructor of type ServiceDependingOn<ConcreteCommand> contains
the parameter with name 'dependency' and type ConcreteCommand that is not
registered. Please ensure ConcreteCommand is registered, or change the constructor of
ServiceDependingOn<ConcreteCommand>. 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<ConcreteCommand>();

// Act
container.GetInstance<ConcreteCommand>();
}

[TestMethod]
public void ResolveUnregisteredConcreteTypes_SetToNever_DoesAllowRegisteredConcreteDependenciesToBeResolved()
{
// Arrange
var container = new Container();
container.Options.ResolveUnregisteredConcreteTypes = false;

container.Register<ConcreteCommand>();
container.Register<ServiceDependingOn<ConcreteCommand>>();

// Act
container.GetInstance<ServiceDependingOn<ConcreteCommand>>();
}

[TestMethod]
public void ResolveUnregisteredConcreteTypes_SetToTrue_DoesAllowUnregisteredConcreteRootTypesToBeResolved()
{
// Arrange
var container = new Container();
container.Options.ResolveUnregisteredConcreteTypes = true;

// Act
container.GetInstance<ConcreteCommand>();
}

[TestMethod]
public void ResolveUnregisteredConcreteTypes_SetToTrue_DoesAllowRegisteredConcreteDependenciesToBeResolved()
{
// Arrange
var container = new Container();
container.Options.ResolveUnregisteredConcreteTypes = true;

// Act
container.GetInstance<ServiceDependingOn<ConcreteCommand>>();
}

[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<ServiceDependingOn<ConcreteCommand>>();
}

public class CompositeService<T> where T : struct
{
public CompositeService(Nullable<T>[] dependencies)
@@ -439,6 +439,7 @@ internal void ThrowParameterTypeMustBeRegistered(InjectionTargetInfo target)
{
throw new ActivationException(
StringResources.ParameterTypeMustBeRegistered(
this,
target,
this.GetNumberOfConditionalRegistrationsFor(target.TargetType),
this.ContainsOneToOneRegistrationForCollectionType(target.TargetType),
@@ -270,6 +270,9 @@ internal Action<object> GetInitializer(Type implementationType, Registration con
return this.GetInstanceProducerForType(serviceType, consumer, buildProducer);
}

internal bool IsConcreteConstructableType(Type concreteType) =>
this.Options.IsConstructableType(concreteType, out string errorMesssage);

private Action<T> GetInitializer<T>(Type implementationType, Registration context)
{
Action<T>[] initializersForType = this.GetInstanceInitializersFor<T>(implementationType, context);
@@ -604,7 +607,8 @@ private InstanceProducer BuildEmptyCollectionInstanceProducerForEnumerable(Type
InjectionConsumerInfo context)
where TConcrete : class
{
if (this.IsConcreteConstructableType(typeof(TConcrete), context))
if (this.Options.ResolveUnregisteredConcreteTypes
&& this.IsConcreteConstructableType(typeof(TConcrete)))
{
return this.GetOrBuildInstanceProducerForConcreteUnregisteredType(typeof(TConcrete), () =>
{
@@ -621,8 +625,11 @@ private InstanceProducer BuildEmptyCollectionInstanceProducerForEnumerable(Type
private InstanceProducer TryBuildInstanceProducerForConcreteUnregisteredType(Type type,
InjectionConsumerInfo context)
{
if (type.IsAbstract() || type.IsValueType() || type.ContainsGenericParameters() ||
!this.IsConcreteConstructableType(type, context))
if (!this.Options.ResolveUnregisteredConcreteTypes
|| type.IsAbstract()
|| type.IsValueType()
|| type.ContainsGenericParameters()
|| !this.IsConcreteConstructableType(type))
{
return null;
}
@@ -672,13 +679,6 @@ private InstanceProducer BuildEmptyCollectionInstanceProducerForEnumerable(Type
return producer;
}

private bool IsConcreteConstructableType(Type concreteType, InjectionConsumerInfo context)
{
string errorMesssage;

return this.Options.IsConstructableType(concreteType, out errorMesssage);
}

// We're registering a service type after 'locking down' the container here and that means that the
// type is added to a copy of the registrations dictionary and the original replaced with a new one.
// This 'reference swapping' is thread-safe, but can result in types disappearing again from the
@@ -753,13 +753,13 @@ private void ThrowMissingInstanceProducerException(Type serviceType)

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);
}
}
@@ -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; }
}

/// <summary>
/// Gets or sets a value indicating whether the container should resolve unregistered concrete types.
/// The default value is <code>true</code>. Consider changing the value to <code>false</code> to prevent
/// accidental creation of types you haven't registered explicitly.
/// </summary>
/// <value>The value indicating whether the container should resolve unregistered concrete types.</value>
/// <exception cref="InvalidOperationException">
/// Thrown when this container instance is locked and can not be altered.
/// </exception>
public bool ResolveUnregisteredConcreteTypes
{
get
{
return this.resolveUnregisteredConcreteTypes;
}

set
{
this.Container.ThrowWhenContainerIsLockedOrDisposed();

this.resolveUnregisteredConcreteTypes = value;
}
}

/// <summary>
/// Gets or sets the constructor resolution behavior. By default, the container only supports types
/// that have a single public constructor.
@@ -261,31 +261,38 @@ internal static string DiagnosticWarningsReported(IList<DiagnosticResult> errors
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(),
GetAdditionalInformationAboutExistingConditionalRegistrations(target, numberOfConditionals),
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(),
GetAdditionalInformationAboutExistingConditionalRegistrations(target, numberOfConditionals),
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) =>
@@ -355,6 +362,16 @@ internal static string TypeMustNotContainInvalidInjectionTarget(InjectionTargetI
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}.",
@@ -994,6 +1011,14 @@ private static string BuildRegistrationName(Tuple<Type, Type, InstanceProducer>
}
}

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);
@@ -23,7 +23,7 @@
namespace SimpleInjector
{
/// <summary>
/// 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.
/// </summary>
public enum VerificationOption
{

0 comments on commit f985eb5

Please sign in to comment.
You can’t perform that action at this time.