Skip to content

Commit

Permalink
Dependency Injection support for ValueTypes as Services (dotnet#59625)
Browse files Browse the repository at this point in the history
* Dependency Injection support for ValueTypes as Services

The only that was thing missing to support ValueTypes as Services
was box the result when visiting constructors call sites.

I am also fixing several tests that were building the ServiceProvider
using the default engine, instead of relying on each specific engine.

fix dotnet#42160

* addressing pr feedback regarding tests
  • Loading branch information
allantargino authored Sep 28, 2021
1 parent 77bd789 commit 52d46c9
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,13 @@ protected override Expression VisitConstructor(ConstructorCallSite callSite, obj
parameterExpressions[i] = Convert(VisitCallSite(callSite.ParameterCallSites[i], context), parameters[i].ParameterType);
}
}
return Expression.New(callSite.ConstructorInfo, parameterExpressions);

Expression expression = Expression.New(callSite.ConstructorInfo, parameterExpressions);
if (callSite.ImplementationType.IsValueType)
{
expression = Expression.Convert(expression, typeof(object));
}
return expression;
}

private static Expression Convert(Expression expression, Type type, bool forceValueTypeConversion = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ protected override object VisitConstructor(ConstructorCallSite constructorCallSi
}

argument.Generator.Emit(OpCodes.Newobj, constructorCallSite.ConstructorInfo);
if (constructorCallSite.ImplementationType.IsValueType)
{
argument.Generator.Emit(OpCodes.Box, constructorCallSite.ImplementationType);
}

return null;
}

Expand Down
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.

using Microsoft.Extensions.DependencyInjection.Specification.Fakes;

namespace Microsoft.Extensions.DependencyInjection.Fakes
{
public struct StructServiceWithNoDependencies: IFakeService
{
public StructServiceWithNoDependencies()
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public void CreatingServiceProviderWithUnresolvableTypesThrows(Type serviceType,
serviceCollection.AddTransient(serviceType, implementationType);

// Act and Assert
var exception = Assert.Throws<ArgumentException>(() => serviceCollection.BuildServiceProvider());
var exception = Assert.Throws<ArgumentException>(() => CreateServiceProvider(serviceCollection));
Assert.Equal(
$"Cannot instantiate implementation type '{implementationType}' for service type '{serviceType}'.",
exception.Message);
Expand All @@ -178,7 +178,7 @@ public void CreatingServiceProviderWithUnresolvableOpenGenericTypesThrows(Type s
serviceCollection.AddTransient(serviceType, implementationType);

// Act and Assert
var exception = Assert.Throws<ArgumentException>(() => serviceCollection.BuildServiceProvider());
var exception = Assert.Throws<ArgumentException>(() => CreateServiceProvider(serviceCollection));
Assert.StartsWith(errorMessage, exception.Message);
}

Expand Down Expand Up @@ -363,7 +363,7 @@ public void GetService_DisposeOnSameThread_Throws()
{
var services = new ServiceCollection();
services.AddSingleton<DisposeServiceProviderInCtor>();
IServiceProvider sp = services.BuildServiceProvider();
IServiceProvider sp = CreateServiceProvider(services);
Assert.Throws<ObjectDisposedException>(() =>
{
// ctor disposes ServiceProvider
Expand All @@ -380,7 +380,7 @@ public void GetAsyncService_DisposeAsyncOnSameThread_ThrowsAndDoesNotHangAndDisp
services.AddSingleton<DisposeServiceProviderInCtorAsyncDisposable>(sp =>
new DisposeServiceProviderInCtorAsyncDisposable(asyncDisposableResource, sp));

var sp = services.BuildServiceProvider();
var sp = CreateServiceProvider(services);
bool doesNotHang = Task.Run(() =>
{
SingleThreadedSynchronizationContext.Run(() =>
Expand All @@ -407,7 +407,7 @@ public void GetService_DisposeOnSameThread_ThrowsAndDoesNotHangAndDisposeGetsCal
services.AddSingleton<DisposeServiceProviderInCtorDisposable>(sp =>
new DisposeServiceProviderInCtorDisposable(disposableResource, sp));

var sp = services.BuildServiceProvider();
var sp = CreateServiceProvider(services);
bool doesNotHang = Task.Run(() =>
{
SingleThreadedSynchronizationContext.Run(() =>
Expand Down Expand Up @@ -447,7 +447,8 @@ public async Task AddDisposablesAndAsyncDisposables_DisposeAsync_AllDisposed(boo
//forces Dispose ValueTask to be asynchronous and not be immediately completed
services.AddSingleton<DelayedAsyncDisposableService>();
}
ServiceProvider sp = services.BuildServiceProvider();
ServiceProvider sp = (ServiceProvider)CreateServiceProvider(services);

var disposable = sp.GetRequiredService<Disposable>();
var asyncDisposable = sp.GetRequiredService<AsyncDisposable>();
DelayedAsyncDisposableService delayedAsyncDisposableService = null;
Expand Down Expand Up @@ -541,7 +542,7 @@ public async Task GetRequiredService_ResolvingSameSingletonInTwoThreads_SameServ
services.AddSingleton<OuterSingleton>();
services.AddSingleton(sp => new InnerSingleton(mreForThread1, mreForThread2));

sp = services.BuildServiceProvider();
sp = CreateServiceProvider(services);

var t1 = Task.Run(() =>
{
Expand Down Expand Up @@ -637,7 +638,7 @@ public async Task GetRequiredService_UsesSingletonAndLazyLocks_NoDeadlock()
});
services.AddSingleton<Thing2>();

sp = services.BuildServiceProvider();
sp = CreateServiceProvider(services);

var t1 = Task.Run(() =>
{
Expand Down Expand Up @@ -717,7 +718,7 @@ public async Task GetRequiredService_BiggerObjectGraphWithOpenGenerics_NoDeadloc
});
services.AddSingleton<Thing5>();

sp = services.BuildServiceProvider();
sp = CreateServiceProvider(services);

// Act
var t1 = Task.Run(() =>
Expand Down Expand Up @@ -794,7 +795,6 @@ public Thing1(Thing0 thing0)

private class Thing0 { }

[ActiveIssue("https://github.com/dotnet/runtime/issues/42160")] // We don't support value task services currently
[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
Expand All @@ -808,7 +808,62 @@ public void WorksWithStructServices(ServiceLifetime lifetime)

var provider = CreateServiceProvider(serviceCollection);
var service = provider.GetService<IFakeMultipleService>();

Assert.NotNull(service);
Assert.IsType<StructFakeMultipleService>(service);
}

[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)]
public void WorksWithFactoryStructServices(ServiceLifetime lifetime)
{
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeService), _ => new StructServiceWithNoDependencies(), lifetime));

var provider = CreateServiceProvider(serviceCollection);
var service = provider.GetService<IFakeService>();

Assert.NotNull(service);
Assert.IsType<StructServiceWithNoDependencies>(service);
}

[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)]
public void WorksWithFactoryStructServicesAsDependencies(ServiceLifetime lifetime)
{
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeService), _ => new StructServiceWithNoDependencies(), lifetime));
serviceCollection.Add(new ServiceDescriptor(typeof(StructService), typeof(StructService), lifetime));
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeMultipleService), typeof(StructFakeMultipleService), lifetime));

var provider = CreateServiceProvider(serviceCollection);
var service = provider.GetService<IFakeMultipleService>();

Assert.NotNull(service);
Assert.IsType<StructFakeMultipleService>(service);
}

[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)]
public void WorksWithIEnumerableStructServices(ServiceLifetime lifetime)
{
IServiceCollection serviceCollection = new ServiceCollection();
for (int i = 0; i < 10; i++)
{
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeService), typeof(StructServiceWithNoDependencies), lifetime));
}

var provider = CreateServiceProvider(serviceCollection);
var services = provider.GetService<IEnumerable<IFakeService>>();

Assert.Equal(10, services.Count());
Assert.All(services, service => Assert.IsType<StructServiceWithNoDependencies>(service));
}

[Fact]
Expand All @@ -821,8 +876,11 @@ public void WorksWithWideScopedTrees()
serviceCollection.AddScoped<IFakeMultipleService, FakeMultipleServiceWithIEnumerableDependency>();
serviceCollection.AddScoped<IFakeService, FakeService>();
}
var serviceProvider = CreateServiceProvider(serviceCollection);

var service = CreateServiceProvider(serviceCollection).GetService<IEnumerable<IFakeOuterService>>();
var services = serviceProvider.GetService<IEnumerable<IFakeOuterService>>();

Assert.Equal(20, services.Count());
}

[Fact]
Expand All @@ -834,7 +892,7 @@ public void GenericIEnumerableItemCachedInTheRightSlot()
// Doesn't matter what this services is, we just want something in the collection after generic registration
services.AddSingleton<FakeService>();

var serviceProvider = services.BuildServiceProvider();
var serviceProvider = CreateServiceProvider(services);

var serviceRef1 = serviceProvider.GetRequiredService<IFakeOpenGenericService<PocoClass>>();
var servicesRef1 = serviceProvider.GetServices<IFakeOpenGenericService<PocoClass>>().Single();
Expand Down Expand Up @@ -1156,9 +1214,9 @@ public async Task GetRequiredService_ResolveUniqueServicesConcurrently_StressTes
[Fact]
public void ScopedServiceResolvedFromSingletonAfterCompilation()
{
ServiceProvider sp = new ServiceCollection()
.AddScoped<A>()
.BuildServiceProvider();
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<A>();
var sp = CreateServiceProvider(serviceCollection);

var singleton = sp.GetRequiredService<A>();
for (int i = 0; i < 10; i++)
Expand All @@ -1168,18 +1226,13 @@ public void ScopedServiceResolvedFromSingletonAfterCompilation()
}
}

[Theory]
[InlineData(ServiceProviderMode.Default)]
[InlineData(ServiceProviderMode.Dynamic)]
[InlineData(ServiceProviderMode.Runtime)]
[InlineData(ServiceProviderMode.Expressions)]
[InlineData(ServiceProviderMode.ILEmit)]
private void ScopedServiceResolvedFromSingletonAfterCompilation2(ServiceProviderMode mode)
{
ServiceProvider sp = new ServiceCollection()
.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<A>, FakeOpenGenericService<A>>()
.BuildServiceProvider(mode);
[Fact]
public void ScopedServiceResolvedFromSingletonAfterCompilation2()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<A>, FakeOpenGenericService<A>>();
var sp = CreateServiceProvider(serviceCollection);

var scope = sp.CreateScope();
for (int i = 0; i < 50; i++)
Expand All @@ -1191,20 +1244,15 @@ private void ScopedServiceResolvedFromSingletonAfterCompilation2(ServiceProvider
Assert.Same(sp.GetRequiredService<IFakeOpenGenericService<A>>().Value, sp.GetRequiredService<A>());
}

[Theory]
[InlineData(ServiceProviderMode.Default)]
[InlineData(ServiceProviderMode.Dynamic)]
[InlineData(ServiceProviderMode.Runtime)]
[InlineData(ServiceProviderMode.Expressions)]
[InlineData(ServiceProviderMode.ILEmit)]
private void ScopedServiceResolvedFromSingletonAfterCompilation3(ServiceProviderMode mode)
[Fact]
public void ScopedServiceResolvedFromSingletonAfterCompilation3()
{
// Singleton IFakeX<A> -> Scoped A -> Scoped Aa
ServiceProvider sp = new ServiceCollection()
.AddScoped<Aa>()
.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<Aa>, FakeOpenGenericService<Aa>>()
.BuildServiceProvider(mode);
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<Aa>()
.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<Aa>, FakeOpenGenericService<Aa>>();
var sp = CreateServiceProvider(serviceCollection);

var scope = sp.CreateScope();
for (int i = 0; i < 50; i++)
Expand Down

0 comments on commit 52d46c9

Please sign in to comment.