This article is an exploration of the core concepts and mechanisms of the Microsoft.Extensions.DependencyInjection Dependency Injection machinery.
The Exploring the Microsoft.Extensions.DependencyInjection machinery project proposes an exploration of the basic concepts and mechanisms of the Microsoft.Extensions.DependencyInjection
Dependency Injection machinery.
This exploration is meant to be progressive, orderly, specifying the terms used, providing in the form of unit tests some as concise as possible examples illustrating the described mechanisms.
The documents used to write this document were mainly:
1) Implementation of a 'Dependency Injection Container
1.1) ServiceDescriptor Class, IServiceCollection and IServiceProvider Interfaces
The implementation of a 'Dependency Injection Container' (aka 'DI container', 'container', 'ServiceProvider
') requires:
-
the definition of a ServiceCollection
, a list of ServiceDescriptors where each element defines the characteristics of one of the services making the container to be produced:
- its type
- the type implementing it
- the lifetime of the instances implementing it (Singleton, Transient, Scoped)
-
the production of a ServiceProvider
from this list.
A ServiceCollection
takes the form of an object instance exposing the IServiceCollection interface.
public interface IServiceCollection: ICollection<ServiceDescriptor>,
IEnumerable<ServiceDescriptor>,
IList<ServiceDescriptor>
A ServiceProvider
takes the form of an object instance exposing the IServiceProvider interface.
public interface IServiceProvider
{
public object? GetService (Type serviceType);
}
The Microsoft.Extensions.DependencyInjection
package provides the ServiceCollection and ServiceProvider classes as default implementations of these interfaces.
1.2) Producing a ServiceProvider from this ServiceCollection list: IServiceCollection.BuildServiceProvider
The ServiceCollectionContainerBuilderExtensions class provides a list of BuildServiceProvider
extensions methods for the IServiceCollection
interface:
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionContainerBuilderExtensions
{
public static ServiceProvider BuildServiceProvider
(this IServiceCollection services);
...
1.3) Examples: class UnitTests.ServiceProviderCreationTests
The UnitTests.ServiceProviderCreationTests unit test class provides examples of various ways to create a ServiceCollection
and then transform it into a ServiceProvider
.
These examples make use of some of the extensions provided by the ServiceCollectionServiceExtensions class described below:
Example: ServiceProviderCreationTests.Test0
public void Test0()
{
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
IServiceCollection _collection = new ServiceCollection();
_collection.Add(new ServiceDescriptor(typeof(InterfaceA),
typeof(ClassA), ServiceLifetime.Singleton));
_collection.AddSingleton<InterfaceB, ClassB>();
_serviceProvider = _collection.BuildServiceProvider();
{
InterfaceA? _interfaceA0 = _serviceProvider.GetService
(typeof(InterfaceA)) as InterfaceA;
InterfaceA? _interfaceA1 = _serviceProvider.GetService
(typeof(InterfaceA)) as InterfaceA;
Assert.NotNull(_interfaceA0);
Assert.Equal(_interfaceA0, _interfaceA1);
}
{
InterfaceB? _interfaceB = _serviceProvider.GetService<InterfaceB>();
Assert.NotNull(_interfaceB);
}
{
ClassD? _classC = _serviceProvider.GetService<ClassD>();
Assert.Null(_classC);
}
}
finally
{
this.Log("_serviceProvider?.Dispose(-)");
_serviceProvider?.Dispose();
this.Log("_serviceProvider?.Dispose(+)");
}
}
Example: ServiceProviderCreationTests.Test1
[Fact]
public void Test1()
{
ServiceProvider? _serviceProvider = null;
try
{
_serviceProvider = new ServiceCollection()
.AddSingleton<InterfaceA, ClassA>()
.BuildServiceProvider();
{
InterfaceA? _interfaceA = _serviceProvider.GetService<InterfaceA>();
}
}
finally
{
_serviceProvider?.Dispose();
}
}
1.4) Organization and content of the IServiceCollection extensions offered by the ServiceCollectionDescriptorExtensions and ServiceCollectionServiceExtensions classes
The ServiceCollectionDescriptorExtensions and ServiceCollectionServiceExtensions classes each expose a set of extensions to the IServiceCollection
interface intended to make the creation of the content of an IServiceCollection
more readable and productive.
The ServiceCollectionDescriptorExtensions
class exposes a set of extension methods of the IServiceCollection
interface concerning its list capabilities: Add
, Remove
, RemoveAll
, Replace
, TryAdd
, TryAddEnumerable
, TryAddScoped
/Singleton
/Transient
...
The ServiceCollectionServiceExtensions
class exposes a set of extension methods of the IServiceCollection
interface interface, grouped by lifetime (Scope
, Singleton
, Transient
) and by the way in which the types of services and their implementation are specified: generic, types passed as parameters.
public static class ServiceCollectionServiceExtensions
{
public static IServiceCollection AddScoped (this IServiceCollection services,
Type serviceType);
public static IServiceCollection AddScoped (this IServiceCollection services,
Type serviceType,
Func<IServiceProvider,object>
implementationFactory);
public static IServiceCollection AddScoped (this IServiceCollection services,
Type serviceType,
Type implementationType);
public static IServiceCollection AddScoped<TService,TImplementation>
(this IServiceCollection services)
where TService: class
where TImplementation: class, TService;
public static IServiceCollection AddScoped<TService,TImplementation>
(this IServiceCollection services,
Func<IServiceProvider,TImplementation>
implementationFactory)
where TService: class
where TImplementation: class, TService;
public static IServiceCollection AddScoped<TService>
(this IServiceCollection services)
where TService: class;
public static IServiceCollection AddScoped<TService>
(this IServiceCollection services,
Func<IServiceProvider,TService>
implementationFactory)
where TService: class;
2) Service Resolution using a IServiceProvider: GetService, GetRequiredService, ...
2.1) The IServiceProvider interface and its ServiceProviderServiceExtensions extension class
-
The IServiceProvider
interface exposes a single method:
public object? GetService (Type serviceType);
-
The ServiceProviderServiceExtensions class provides many extensions to this interface as variations of GetService
, GetRequiredService
, GetServices
, CreateScope
, CreateAsyncScope
:
public static T? GetService(this IServiceProvider provider);
public static object GetRequiredService(this IServiceProvider provider,
Type serviceType);
public static T GetRequiredService(this IServiceProvider provider)
where T: notnull;
public static IEnumerable GetServices(this IServiceProvider provider);
public static IEnumerable<object?> GetServices
(this IServiceProvider provider, Type serviceType);
public static IServiceScope CreateScope(this IServiceProvider provider);
public static AsyncServiceScope CreateAsyncScope
(this IServiceProvider provider);
public static AsyncServiceScope CreateAsyncScope
(this IServiceScopeFactory serviceScopeFactory);
2.2) GetService vs GetRequiredService
The difference between a GetService
method and its GetRequiredService
counterpart is that:
GetService
returns null
if the requested service cannot be resolved by the IServiceProvider
interface. GetRequiredService
triggers an exception in this case.
It is illustrated by the ServiceProviderServiceExtensionsTests.Test_GetService unit test.
2.3) GetServices
Example: ServiceProviderServiceExtensionsTests.Test_GetServices
The following code:
public void Test_GetServices()
{
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
_serviceProvider = new ServiceCollection()
.AddSingleton<ClassA>(new ClassA())
.AddSingleton<ClassA>(new ClassA())
.AddSingleton<ClassA>(new ClassA())
.BuildServiceProvider();
ClassA classA = _serviceProvider.GetService<ClassA>();
this.Log($"classA={classA}");
foreach (ClassA _service in _serviceProvider.GetServices<ClassA>())
{
this.Log($"_service={_service}");
}
}
finally
{
this.Log("(+)");
_serviceProvider?.Dispose();
}
}
produces the following debug output:
[16]UnitTests.Tests.ServiceProviderServiceExtensionsTests].(Test_GetServices) '(-)'
[16]UnitTests.Tests.ServiceProviderServiceExtensionsTests].
(Test_GetServices) 'classA=ClassA[4]'
[16]UnitTests.Tests.ServiceProviderServiceExtensionsTests].
(Test_GetServices) '_service=ClassA[2]'
[16]UnitTests.Tests.ServiceProviderServiceExtensionsTests].
(Test_GetServices) '_service=ClassA[3]'
[16]UnitTests.Tests.ServiceProviderServiceExtensionsTests].
(Test_GetServices) '_service=ClassA[4]'
[16]UnitTests.Tests.ServiceProviderServiceExtensionsTests].(Test_GetServices) '(+)'
3) Service Implementation Lifetime: Singleton, Transient, Scope
The rules presented in this paragraph are illustrated by the ServiceLifetimeTests class.
3.1) Terminology
-
Resolution of a service by a (DI) container:
Calling the .GetService
method of an IServiceProvider
instance specifying the type of the service for which you wish to obtain an implementation
-
Resolution of a Singleton/Transient/Scope service by a (DI) container:
Resolution of a service that has been registered as Singleton/Transient/Scope by the ServiceCollection
from which the implemented DI Container (IServiceProvider
) was produced (IServiceCollection.BuildServiceProvider
).
-
'root' container:
An IServiceProvider
instance produced from an instance of IServiceCollection
, by a call to BuildServiceProvider
.
-
'scoped' container:
The IServiceProvider
instance exposed by an IServiceScope
instance:
public interface IServiceScope: IDisposable
{
IServiceProvider ServiceProvider
{
get;
}
}
See below.
3.2) Singleton
A single instance of the type implementing a 'Singleton' service is created by a ServiceProvider
on the first resolution request (GetService
).
A 'Singleton' service could also be associated to an implementation instance when the ServiceCollection
from which the ServiceProvider
originates was created. This implementation instance will then be returned by the ServiceProvider
as a resolution of the 'Singleton' service.
In both cases, the resolution of a 'Singleton' service always provides the same answer.
Example
ServiceProvider? _serviceProvider = null;
try
{
ClassD _classD = new ClassD();
_serviceProvider = new ServiceCollection()
.AddSingleton<InterfaceA, ClassA>()
.AddSingleton<ClassD>(_classD)
.BuildServiceProvider();
{
InterfaceA? _interface0 = _serviceProvider.GetService<InterfaceA>();
Assert.NotNull(_interface0);
InterfaceA? _interface1 = _serviceProvider.GetService<InterfaceA>();
Assert.Equal(_interface1, _interface0);
ClassD? _class0 = _serviceProvider.GetService<ClassD>();
Assert.Equal(_class0, _classD);
}
3.3) Transient
The resolution of a 'Transient' service provides each time a new instance.
Example
ServiceProvider? _serviceProvider = null;
try
{
_serviceProvider = new ServiceCollection()
.AddTransient<InterfaceB, ClassB>()
.BuildServiceProvider();
{
InterfaceB? _interface0 = _serviceProvider.GetService<InterfaceB>();
Assert.NotNull(_interface0);
InterfaceB? _interface1 = _serviceProvider.GetService<InterfaceB>();
Assert.NotEqual(_interface1, _interface0);
}
3.4) Scope
Rule: The resolution of a 'Scope' service must not be requested from a 'root' container but from a 'scoped' container.
This rule can be checked at runtime or not by a ServiceProvider
depending on how it was produced (see below).
Example
bool validateScopes = true;
ServiceProvider? _serviceProvider = null;
try
{
ServiceProviderOptions serviceProviderOptions = new ServiceProviderOptions()
{
ValidateScopes = validateScopes,
};
ClassD _classD = new ClassD();
_serviceProvider = new ServiceCollection()
.AddScoped<ClassC>()
.BuildServiceProvider(options: serviceProviderOptions);
{
bool _thrown = false;
try
{
ClassC? _class0 = _serviceProvider.GetService<ClassC>();
ClassC? _class1 = _serviceProvider.GetService<ClassC>();
Assert.Equal(_class0, _class1);
}
catch (Exception E)
{
_thrown = true;
this.Log(E);
}
Assert.Equal(_thrown, validateScopes);
}
4) Choice by a Container of the Constructor of the Type Implementing a Service
The resolution of a service by a container can imply the creation of an instance of the type implementing this service. This is the case, among others, during the first resolution of a 'Singleton' service or during each resolution of a 'Transient' service.
It is possible that the class to be instantiated exposes several constructors: which one does a container choose when it instantiates the implementing class?
A container chooses the constructor whose parameter list contains the largest number of types resolved by itself. It is possible that several constructors quote the same number of resolved types: in this case, the container does not know how to choose a constructor and therefore does not instantiate the class and throws an exception.
The ServiceInstantiationTests.Test_ConstructorChoice test illustrates these mechanisms.
Example
class ClassD: BaseClass
{
public ClassD()
{
this.Log("");
}
public ClassD(InterfaceA interfaceA)
{
this.Log($"interfaceA={interfaceA}");
}
public ClassD(InterfaceA interfaceA, InterfaceB interfaceB)
{
this.Log($"interfaceA={interfaceA} interfaceB={interfaceB}");
}
}
class ClassE: BaseClass
{
public ClassE(InterfaceA interfaceA, InterfaceB interfaceB)
{
this.Log("");
}
public ClassE(InterfaceA interfaceA, InterfaceC interfaceC)
{
this.Log("");
}
}
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
_serviceProvider = new ServiceCollection()
.AddTransient<InterfaceA, ClassA>()
.AddTransient<InterfaceB, ClassB>()
.AddTransient<InterfaceC, ClassC>()
.AddTransient<ClassD>()
.AddTransient<ClassE>()
.BuildServiceProvider();
{
this.Log("_serviceProvider.GetService<ClassD>(-)");
ClassD? classD = _serviceProvider.GetService<ClassD>();
this.Log($"_serviceProvider.GetService<ClassD>(+) classD={classD}");
}
{
bool _thrown = false;
try
{
this.Log("_serviceProvider.GetService<ClassE>(-)");
ClassE? classE = _serviceProvider.GetService<ClassE>();
this.Log($"_serviceProvider.GetService<ClassE>(+) classE={classE}");
}
catch (Exception E)
{
_thrown = true;
this.Log(E);
}
Assert.True(_thrown);
}
5) Registering and Destroying Disposable Instances Generated by a Container
As mentioned before, the resolution of a service by a container can imply the creation of an instance of the type implementing this service.
These instances may be registered by the container in an internal list to ensure their Singleton or Scope character, but also to explicitly destroy the 'disposable' instances (exposing the IDisposable
interface) it creates, regardless of their Singleton/Transient/Scoped lifetime, when it is destroyed.
The ServiceProvider
class is disposable:
public sealed class ServiceProvider: IServiceProvider, IDisposable, IAsyncDisposable
The IServiceScope
interface is disposable:
public interface IServiceScope: IDisposable
A 'root' container is explicitly 'disposable'.
A 'scoped' container is destroyed when its scope is destroyed.
The explicit destruction of the 'disposable' instances produced and listed by a container occurs when the container is 'disposed'.
Examples
'root' container
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
_serviceProvider = new ServiceCollection()
.AddTransient<DisposableClassA>()
.BuildServiceProvider();
DisposableClassA disposableClassA = _serviceProvider.GetService<DisposableClassA>();
this.Log($"disposableClassA={disposableClassA}");
}
finally
{
this.Log("_serviceProvider?.Dispose(-)");
_serviceProvider?.Dispose();
this.Log("_serviceProvider?.Dispose(+)");
}
'scoped' container
The following code:
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
_serviceProvider = new ServiceCollection()
.AddSingleton<DisposableClassA>()
.AddTransient<DisposableClassB>()
.AddScoped<DisposableClassC>()
.BuildServiceProvider();
using (IServiceScope scope = _serviceProvider.CreateScope())
{
DisposableClassA? _disposableClassA =
_serviceProvider.GetService<DisposableClassA>();
this.Log($"_disposableClassA={_disposableClassA}");
DisposableClassB? _disposableClassB =
_serviceProvider.GetService<DisposableClassB>();
this.Log($"_disposableClassB={_disposableClassB}");
DisposableClassC? _disposableClassC =
_serviceProvider.GetService<DisposableClassC>();
this.Log($"_disposableClassC={_disposableClassC}");
}
}
finally
{
this.Log("_serviceProvider?.Dispose(-)");
_serviceProvider?.Dispose();
this.Log("_serviceProvider?.Dispose(+)");
}
produces the following debug output:
[16]UnitTests.Tests.ServiceInstantiationTests].(Test_DisposableImplementations1) '(-)'
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableImplementations1) '_disposableClassA=DisposableClassA[2]'
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableImplementations1) '_disposableClassB=DisposableClassB[3]'
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableImplementations1) '_disposableClassC=DisposableClassC[4]'
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableImplementations1) '_serviceProvider?.Dispose(-)'
[16]DisposableClassC[4]].(dispose) 'disposing=True'
[16]DisposableClassB[3]].(dispose) 'disposing=True'
[16]DisposableClassA[2]].(dispose) 'disposing=True'
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableImplementations1) '_serviceProvider?.Dispose(+)'
Note: The 'non disposable' Transient instances produced by a container are not listed, they are released by the Garbage Collector.
Some important consequences of this operation:
-
a 'root' container resolving Singleton or Transient services as 'disposable' instances can be a source of memory leakage, especially for Transients since they won't be disposed until the container is itself disposed.
-
this is also true of Transient and Scope services resolved in the form of 'disposable' instances by a 'scoped' container, but this container will be destroyed at the same time as and by its parent scope, which is supposed to happen quickly.
-
services resolved as disposable instances should not be disposed by the client of the container that issued them: it will be done by the container itself.
This last point should encourage to avoid storing as class members the references of resolved services exposing the IDisposable
interface.
Example
class DisposableClassA: Disposable0, IDisposableA
{
}
class DisposableClassE: Disposable0
{
IDisposableA? _disposableA;
public DisposableClassE(IDisposableA disposableA)
{
this._disposableA = disposableA;
}
protected override void doDispose(bool disposing)
{
try
{
this.Log($"(-) disposing={disposing} _disposableA={_disposableA}");
if (disposing)
{
_disposableA?.Dispose();
}
this.Log($"(+) disposableA={_disposableA}");
}
finally
{
base.doDispose(disposing);
}
}
}
public void Test_DisposableService0()
{
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
_serviceProvider = new ServiceCollection()
.AddTransient<IDisposableA, DisposableClassD>()
.AddTransient<DisposableClassE>()
.BuildServiceProvider();
DisposableClassE disposableClassE =
_serviceProvider.GetService<DisposableClassE>();
this.Log($"disposableClassE={disposableClassE}");
}
finally
{
this.Log("_serviceProvider?.Dispose(-)");
_serviceProvider?.Dispose();
this.Log("_serviceProvider?.Dispose(+)");
}
}
Test_DisposableService0
produces the following debug output:
[16]UnitTests.Tests.ServiceInstantiationTests].(Test_DisposableService0) '(-)'
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableService0) 'disposableClassE=DisposableClassE[3]'
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableService0) '_serviceProvider?.Dispose(-)'
[16]DisposableClassE[3]].(dispose) 'disposing=True'
[16]DisposableClassE[3]].(doDispose) '(-)
disposing=True _disposableA=DisposableClassD[2]'
[16]DisposableClassA[2]].(dispose) 'disposing=True'
[16]DisposableClassE[3]].(doDispose) '(+) disposableA=DisposableClassD[2]'
[16]DisposableClassA[2]].(dispose)
'disposing=True ALREADY DISPOSED' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
[16]UnitTests.Tests.ServiceInstantiationTests].
(Test_DisposableService0) '_serviceProvider?.Dispose(+)'
6) Open Generic Services
A container can register and resolve 'open generic services'.
Example
class UnitTests.Tests.GenericServicesTests
{
public void Test_GenericService0()
{
ServiceProvider services = null;
try
{
services = new ServiceCollection()
.AddScoped(typeof(InterfaceF<>), typeof(ClassF<>))
.BuildServiceProvider();
InterfaceF<int>? _instance0 = services.GetService<InterfaceF<int>>();
Assert.NotNull(_instance0);
InterfaceF<string>? _instance1 = services.GetService<InterfaceF<string>>();
Assert.NotNull(_instance1);
}
finally
{
services?.Dispose();
}
}
7) Validation Capabilities of a Container
7.1) Preventing the Resolution of Scoped Services out of a Scope
Example: ServiceProviderValidationTests.TestScopeValidation0
public class ServiceProviderValidationTests
{
[Fact]
public void TestScopeValidation0()
{
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
_serviceProvider = new ServiceCollection()
.AddScoped<ClassA>()
.BuildServiceProvider(validateScopes: true);
{
bool _exception = false;
try
{
ClassA classA = _serviceProvider.GetService<ClassA>();
}
catch (Exception e)
{
_exception = true;
this.Log(e);
}
Assert.True(_exception);
}
using (IServiceScope scope = _serviceProvider.CreateScope())
{
bool _exception = false;
try
{
ClassA classA = scope.ServiceProvider.GetService<ClassA>();
Assert.NotNull(classA);
}
catch (Exception e)
{
_exception = true;
this.Log(e);
}
Assert.False(_exception);
}
}
finally
{
this.Log("(+)");
_serviceProvider?.Dispose();
}
}
7.2) Preventing the Resolution of a Singleton Service Depending on Scope Services
Example: ServiceProviderValidationTests.TestScopeValidation1
public class ServiceProviderValidationTests
{
[Fact]
public void TestScopeValidation1()
{
ServiceProvider? _serviceProvider = null;
try
{
this.Log("(-)");
_serviceProvider = new ServiceCollection()
.AddSingleton<ClassE>()
.AddScoped<InterfaceA, ClassA>()
.BuildServiceProvider(validateScopes: true);
{
bool _exception = false;
try
{
ClassE classE = _serviceProvider.GetService<ClassE>();
}
catch (Exception e)
{
_exception = true;
this.Log(e);
}
Assert.True(_exception);
}
}
finally
{
this.Log("(+)");
_serviceProvider?.Dispose();
}
}
7.3) Checking the Completeness of the Description of the Services Meant to Build a Container
Example: ServiceProviderValidationTests.TestScopeValidation2
public class ServiceProviderValidationTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void TestScopeValidation2(bool validateOnBuild)
{
ServiceProvider? _serviceProvider = null;
try
{
this.Log($"(-) validateOnBuild={validateOnBuild}");
{
bool _exception = false;
try
{
_serviceProvider = new ServiceCollection()
.AddSingleton<ClassE>()
.BuildServiceProvider(new ServiceProviderOptions()
{
ValidateOnBuild = validateOnBuild
});
bool __exception = false;
try
{
ClassE classE = _serviceProvider.GetService<ClassE>();
}
catch (Exception e)
{
__exception = true;
this.Log(e);
}
Assert.True(__exception);
}
catch (Exception e)
{
_exception = true;
}
finally
{
Assert.Equal(_exception, validateOnBuild);
}
}
}
finally
{
this.Log("(+)");
_serviceProvider?.Dispose();
}
}
8) The ActivatorUtilities Class
The ActivatorUtilities class allows to create instances of classes not resolved by a container but whose constructor requires arguments that can be resolved by this container.
This excellent article explains how: Activator utilities: activate anything!
9) The IServiceScopeFactory Interface
The IServiceScopeFactory interface exposes only one method:
public Microsoft.Extensions.DependencyInjection.IServiceScope CreateScope ();
It is registered as a Singleton service by a container and can be resolved and used to produce IServiceScope
scopes.
Example: IServiceScopeFactoryTests.Test0
void test0(IServiceScope scope)
{
InterfaceA? _pInterfaceA0 = scope.ServiceProvider.GetService<InterfaceA>();
InterfaceA? _pInterfaceA1 = scope.ServiceProvider.GetService<InterfaceA>();
}
void test1(IServiceScopeFactory serviceScopeFactory)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
InterfaceA? _interfaceA0 = scope.ServiceProvider.GetService<InterfaceA>();
}
}
[Fact]
public void Test0()
{
ServiceProvider services = null;
try
{
this.Log($"(-)");
services = new ServiceCollection()
.AddScoped<InterfaceA, ClassA>()
.BuildServiceProvider(validateScopes: true);
using (IServiceScope scope = services.CreateScope())
{
test0(scope);
}
test1(services.GetService<IServiceScopeFactory>());
}
catch (Exception E)
{
this.Log(E);
}
finally
{
this.Log("services?.Dispose(-)");
services?.Dispose();
this.Log("services?.Dispose(+)");
}
}