Skip to content

Instantly share code, notes, and snippets.

@fatihmemis
Last active May 6, 2023 01:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fatihmemis/bd1e9853a3b454788395a8921596d974 to your computer and use it in GitHub Desktop.
Save fatihmemis/bd1e9853a3b454788395a8921596d974 to your computer and use it in GitHub Desktop.
Service lifetime samples for Asp.Net Core dependency injection.
public static void Run()
{
var serviceBuilder = new ServiceCollection();
serviceBuilder
.AddSingleton<SingletonDisposableService>()
.AddTransient<TransientDisposableService>()
.AddScoped<ScopedDisposableService>()
;
// You should use validateScopes: true as a best practice. It's set to false here for demo purposes.
using var services = serviceBuilder.BuildServiceProvider(validateScopes: false);
var logger = new TestLogger();
// Tests in Scope
logger.Header("Creating a service scope...");
using (var scope = services.CreateScope())
{
// Resolve scoped service twice and compare reference
// Resolve scoped service first time
logger.Header("Resolving first scoped service in service scope...");
var scopedService1 = scope.ServiceProvider.GetService<ScopedDisposableService>();
scopedService1.SayHello();
logger.Separator();
// Resolve scoped service second time
logger.Header("Resolving second scoped service in service scope...");
var scopedService2 = scope.ServiceProvider.GetService<ScopedDisposableService>();
scopedService2.SayHello();
logger.Separator();
// Compare references of two scope services and print the result
var scopedServicesAreSame = Object.ReferenceEquals(scopedService1, scopedService2);
logger.Log($"{nameof(scopedService1)} { (scopedServicesAreSame ? "==" : "!=") } {nameof(scopedService2)}");
// Resolve transient service
logger.Header("Resolving transient service in service scope...");
var transientInScope = scope.ServiceProvider.GetService<TransientDisposableService>();
transientInScope.SayHello();
logger.Separator();
// Resolve sigleton service
logger.Header("Resolving singleton service in service scope...");
var singletonInScope = scope.ServiceProvider.GetService<SingletonDisposableService>();
singletonInScope.SayHello();
logger.Separator();
}
logger.Log("Service scope disposed.", spaceAfter: true);
// Resolve tests in root scope
logger.Header("Testing in root scope");
// Singleton service
logger.Log("Resolving singleton service in ROOT scope...");
var singletonService = services.GetService<SingletonDisposableService>();
singletonService.SayHello();
logger.Separator();
// Transient service
logger.Log("Resolving transient service in ROOT scope...");
var transientService = services.GetService<TransientDisposableService>();
transientService.SayHello();
logger.Separator();
// Scoped service
logger.Log("Resolving scope service in ROOT scope...");
// If validateScopes parameter is true, [ ex: serviceBuilder.BuildServiceProvider(validateScopes: true) ]
// This line will throw an exception, because scope services will not be allowed in the root scope.
// In this test we used validateScopes: false for demo purposes.
var scopedService = services.GetService<ScopedDisposableService>();
scopedService.SayHello();
logger.Separator();
logger.Header("Disposing services...");
// Although we used "using..." keyword as a best practice while building services, we explicitly dispose it for demo purposes
services.Dispose();
logger.Log("Services disposed.");
logger.Separator();
}
public class ScopedDisposableService : DisposableServiceBase
{
public ScopedDisposableService(TransientDisposableService transientDisposableService, SingletonDisposableService singletonDisposableService)
{
TransientDisposableService = transientDisposableService;
SingletonDisposableService = singletonDisposableService;
}
public TransientDisposableService TransientDisposableService { get; }
public SingletonDisposableService SingletonDisposableService { get; }
}
public class SingletonDisposableService : DisposableServiceBase { }
public class TransientDisposableService : DisposableServiceBase { }
// Container tracks instance if service is registered by implemented type.
services.AddSingleton<IMyDisposableSingletonService, MyDisposableSingletonService>();
// Container tracks instance if service is registered by factory function.
services.AddSingleton<IMyDisposableSingletonService>(sp => new MyDisposableSingletonService());
// Container does NOT track if service is registered by instance
services.AddSingleton<IMyDisposableSingletonService>(new MyDisposableSingletonService());
public interface IMyDisposableTransientServiceFactory
{
IMyDisposableTransientService CreateNew(IServiceProvider services);
}
// Usage
class MyService
{
IServiceProvider _services;
IMyDisposableTransientServiceFactory _transientServiceFactory;
public MyService(IServiceProvider services, IMyDisposableTransientServiceFactory transientServiceFactory)
{
// Code omitted
}
public void DoSomeStuff(IEnumerable<TaskNotification> tasks)
{
foreach (var task in tasks) {
using var transientService = _transientServiceFactory.CreateNew(_services);
// ...
}
// or create a new scope for the batch operation for dependent services used by IMyDisposableTransientService
using var scope = _services.GetRequiredService<IServiceScopeFactory>().CreateScope();
foreach (var task in tasks) {
using var transientService = _transientServiceFactory.CreateNew(scope.ServiceProvider);
// ...
}
}
}
// If you need faster application warmups
// instead of registering startup services by type
// services.AddTransient<MyService>();
// Register them by service factory
services.AddTransient<MyService>(sp => new MyService(sp.GetRequiredService<IMyDependencyService>()));
static class Program {
static void Main() {
var services = new ServiceCollection();
services.AddTransient<TransientService>();
services.AddScoped<ScopedService>();
using ServiceProvider serviceProvider = services.BuildServiceProvider();
// Since it is resolved in root scope, Scoped service will act like a singleton
// Transient service which is a dependency of ScopedService will also be not disposed until the service provider is disposed.
var avoidToResolveLikeThisScopedService = serviceProvider.GetService<ScopedService>();
// Correct usage
using (var scope = serviceProvider.CreateScope()) {
var resolvedScopedService = scope.ServiceProvider.GetService<ScopedService>();
// Do something with the resolved service
}
// ... some other code
}
}
// ...
sealed class TransientService : IDisposable {
public void Dispose() {}
}
class ScopedService {
public ScopedService(TransientService transientService){
// ...
}
}
static class Program {
static void Main() {
var services = new ServiceCollection();
services.AddSingleton<SingletonService>();
services.AddScoped<ScopedService>();
using ServiceProvider serviceProvider = services.BuildServiceProvider(validateScopes: true);
// This will cause an exception
var resolvedSingletonService = serviceProvider.GetService<SingletonService>();
}
}
class SingletonService {
public SingletonService(ScopedService scopedService)
{
// ...
}
}
class ScopedService {}
public class MyService {
public MyService(IServiceProvider services) {
this._logger = services.GetRequiredService<ILogger<MyService>>();
}
}
public class MyService {
public MyService() {
this._logger = ServiceLocator.Services.GetRequiredService<ILogger<MyService>>();
}
}
public class MyService {
public MyService(ILogger<MyService> logger) {
this._logger = logger;
}
}
[ApiController]
public class EventsController: ConrollerBase {
public EventsController(ISomeCommonService commonService) {
_commonService = commonService;
}
[HttpGet("{id}")]
public ActionResult<EventModel> Get(string id){
// ... code using ISomeCommonService
}
[HttpPost("uncommon-action")]
public ActionResult UncommonAction([FromServices]IPrivateService privateService, [FromBody]MyData data){
// ... code using IPrivateService
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment