Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,7 @@
<data name="AsyncDisposableServiceDispose" xml:space="preserve">
<value>'{0}' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.</value>
</data>
<data name="GenericConstraintViolation" xml:space="preserve">
<value>Generic constraints violated for type '{0}' while attempting to activate '{1}'.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private ServiceCallSite TryCreateOpenGeneric(Type serviceType, CallSiteChain cal
if (serviceType.IsConstructedGenericType
&& _descriptorLookup.TryGetValue(serviceType.GetGenericTypeDefinition(), out ServiceDescriptorCacheItem descriptor))
{
return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot);
return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot, true);
}

return null;
Expand Down Expand Up @@ -164,7 +164,7 @@ private ServiceCallSite TryCreateEnumerable(Type serviceType, CallSiteChain call
{
ServiceDescriptor descriptor = _descriptors[i];
ServiceCallSite callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot) ??
TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot);
TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot, false);

if (callSite != null)
{
Expand Down Expand Up @@ -230,14 +230,28 @@ private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type servic
return null;
}

private ServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
private ServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
{
if (serviceType.IsConstructedGenericType &&
serviceType.GetGenericTypeDefinition() == descriptor.ServiceType)
{
Debug.Assert(descriptor.ImplementationType != null, "descriptor.ImplementationType != null");
var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
Type closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
Type closedType;
try
{
closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
}
catch (ArgumentException ex)
{
if (throwOnConstraintViolation)
{
throw new InvalidOperationException(SR.Format(SR.GenericConstraintViolation, serviceType, descriptor.ImplementationType), ex);
}

return null;
}

return CreateConstructorCallSite(lifetime, serviceType, closedType, callSiteChain);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@

namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class AutofacDependencyInjectionSpecificationTests: DependencyInjectionSpecificationTests
public class AutofacDependencyInjectionSpecificationTests : SkippableDependencyInjectionSpecificationTests
{
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
public override string[] SkippedTests => new[]
{
"PublicNoArgCtorConstrainedOpenGenericServicesCanBeResolved"
};

protected override IServiceProvider CreateServiceProviderImpl(IServiceCollection serviceCollection)
{
var builder = new ContainerBuilder();
builder.Populate(serviceCollection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@

namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class StashBoxDependencyInjectionSpecificationTests: DependencyInjectionSpecificationTests
public class StashBoxDependencyInjectionSpecificationTests : SkippableDependencyInjectionSpecificationTests
{
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
public override string[] SkippedTests => new[]
{
"PublicNoArgCtorConstrainedOpenGenericServicesCanBeResolved",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we skipping these tests? Same questions for the one in Autofac

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At one point, AutoFac didn't support constraints on new. I'm not sure that's relevant though, type constraints the useful bit for services. I'll double check though, it's been a few years since I tried this example when it failed.

The others were similar, they all worked with the desired scenarios, but not necessarily all constraints (like struct). I'll update and retry those.

"SelfReferencingConstrainedOpenGenericServicesCanBeResolved",
"ClassConstrainedOpenGenericServicesCanBeResolved"
};

protected override IServiceProvider CreateServiceProviderImpl(IServiceCollection serviceCollection)
{
return serviceCollection.UseStashbox();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,160 @@ public void OpenGenericServicesCanBeResolved()
Assert.Same(singletonService, genericService.Value);
}

[Fact]
public void ConstrainedOpenGenericServicesCanBeResolved()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ConstrainedFakeOpenGenericService<>));
var poco = new PocoClass();
collection.AddSingleton(poco);
collection.AddSingleton<IFakeSingletonService, FakeService>();
var provider = CreateServiceProvider(collection);
// Act
var allServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<IFakeSingletonService>>().ToList();
var singletonService = provider.GetService<IFakeSingletonService>();
// Assert
Assert.Equal(2, allServices.Count);
Assert.Same(poco, allServices[0].Value);
Assert.Same(poco, allServices[1].Value);
Assert.Equal(1, constrainedServices.Count);
Assert.Same(singletonService, constrainedServices[0].Value);
}

[Fact]
public void ConstrainedOpenGenericServicesReturnsEmptyWithNoMatches()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ConstrainedFakeOpenGenericService<>));
collection.AddSingleton<IFakeSingletonService, FakeService>();
var provider = CreateServiceProvider(collection);
// Act
var constrainedServices = provider.GetServices<IFakeOpenGenericService<IFakeSingletonService>>().ToList();
// Assert
Assert.Equal(0, constrainedServices.Count);
}

[Fact]
public void InterfaceConstrainedOpenGenericServicesCanBeResolved()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithInterfaceConstraint<>));
var enumerableVal = new ClassImplementingIEnumerable();
collection.AddSingleton(enumerableVal);
collection.AddSingleton<IFakeSingletonService, FakeService>();
var provider = CreateServiceProvider(collection);
// Act
var allServices = provider.GetServices<IFakeOpenGenericService<ClassImplementingIEnumerable>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<IFakeSingletonService>>().ToList();
var singletonService = provider.GetService<IFakeSingletonService>();
// Assert
Assert.Equal(2, allServices.Count);
Assert.Same(enumerableVal, allServices[0].Value);
Assert.Same(enumerableVal, allServices[1].Value);
Assert.Equal(1, constrainedServices.Count);
Assert.Same(singletonService, constrainedServices[0].Value);
}

[Fact]
public void PublicNoArgCtorConstrainedOpenGenericServicesCanBeResolved()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNewConstraint<>));
var provider = CreateServiceProvider(collection);
// Act
var allServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<ClassWithPrivateCtor>>().ToList();
// Assert
Assert.Equal(2, allServices.Count);
Assert.Equal(1, constrainedServices.Count);
}

[Fact]
public void ClassConstrainedOpenGenericServicesCanBeResolved()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithClassConstraint<>));
var provider = CreateServiceProvider(collection);
// Act
var allServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<int>>().ToList();
// Assert
Assert.Equal(2, allServices.Count);
Assert.Equal(1, constrainedServices.Count);
}

[Fact]
public void StructConstrainedOpenGenericServicesCanBeResolved()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithStructConstraint<>));
var provider = CreateServiceProvider(collection);
// Act
var allServices = provider.GetServices<IFakeOpenGenericService<int>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList();
// Assert
Assert.Equal(2, allServices.Count);
Assert.Equal(1, constrainedServices.Count);
}

[Fact]
public void AbstractClassConstrainedOpenGenericServicesCanBeResolved()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithAbstractClassConstraint<>));
var poco = new PocoClass();
collection.AddSingleton(poco);
var classInheritingClassInheritingAbstractClass = new ClassInheritingClassInheritingAbstractClass();
collection.AddSingleton(classInheritingClassInheritingAbstractClass);
var provider = CreateServiceProvider(collection);
// Act
var allServices = provider.GetServices<IFakeOpenGenericService<ClassInheritingClassInheritingAbstractClass>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList();
// Assert
Assert.Equal(2, allServices.Count);
Assert.Same(classInheritingClassInheritingAbstractClass, allServices[0].Value);
Assert.Same(classInheritingClassInheritingAbstractClass, allServices[1].Value);
Assert.Equal(1, constrainedServices.Count);
Assert.Same(poco, constrainedServices[0].Value);
}

[Fact]
public void SelfReferencingConstrainedOpenGenericServicesCanBeResolved()
{
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithSelfReferencingConstraint<>));
var poco = new PocoClass();
collection.AddSingleton(poco);
var selfComparable = new ClassImplementingIComparable();
collection.AddSingleton(selfComparable);
var provider = CreateServiceProvider(collection);
// Act
var allServices = provider.GetServices<IFakeOpenGenericService<ClassImplementingIComparable>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList();
// Assert
Assert.Equal(2, allServices.Count);
Assert.Same(selfComparable, allServices[0].Value);
Assert.Same(selfComparable, allServices[1].Value);
Assert.Equal(1, constrainedServices.Count);
Assert.Same(poco, constrainedServices[0].Value);
}

[Fact]
public void ClosedServicesPreferredOverOpenGenericServices()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public abstract class AbstractClass
{

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassImplementingIComparable : IComparable<ClassImplementingIComparable>
{
public int CompareTo(ClassImplementingIComparable other) => 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections;

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassImplementingIEnumerable : IEnumerable
{
public IEnumerator GetEnumerator() => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassInheritingAbstractClass : AbstractClass
{

}

public class ClassAlsoInheritingAbstractClass : AbstractClass
{

}

public class ClassInheritingClassInheritingAbstractClass : ClassInheritingAbstractClass
{

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassWithAbstractClassConstraint<T> : IFakeOpenGenericService<T>
where T : AbstractClass
{
public ClassWithAbstractClassConstraint(T value) => Value = value;

public T Value { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassWithClassConstraint<T> : IFakeOpenGenericService<T>
where T : class
{
public T Value { get; } = default;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections;

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassWithInterfaceConstraint<T> : IFakeOpenGenericService<T>
where T : IEnumerable
{
public ClassWithInterfaceConstraint(T value) => Value = value;

public T Value { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassWithNewConstraint<T> : IFakeOpenGenericService<T>
where T : new()
{
public T Value { get; } = new T();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public class ClassWithNoConstraints<T> : IFakeOpenGenericService<T>
{
public T Value { get; } = default;
}
}
Loading