Understanding and Using Scope in .NET Core

The scope is a crucial concept in .NET Core dependency injection (DI). It determines the lifecycle and visibility of the services that are injected into your application. Understanding how scope works can help you manage resources more efficiently and avoid common pitfalls, such as memory leaks and unnecessary object creation. In this article, we will explore what scope is, the different types of scopes available in .NET Core, and how to use them with practical examples.

Types of Scopes in .NET Core

  1. Transient: A new instance of the service is created every time it is requested.
  2. Scoped: A single instance of the service is created per request (or per scope).
  3. Singleton: A single instance of the service is created and shared throughout the application's lifetime.

Using Scope in .NET Core

Let's dive into how to use these scopes with examples.

1. Transient

A transient service is created each time it is requested. This is useful for lightweight, stateless services.

public interface ITransientService
{
    Guid GetOperationId();
}
public class TransientService : ITransientService
{
    private readonly Guid _operationId;
    public TransientService()
    {
        _operationId = Guid.NewGuid();
    }
    public Guid GetOperationId() => _operationId;
}

Register the service in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ITransientService, TransientService>();
}

Use the service in a controller

[ApiController]
[Route("api/[controller]")]
public class ExampleController : ControllerBase
{
    private readonly ITransientService _transientService1;
    private readonly ITransientService _transientService2;
    public ExampleController(
        ITransientService transientService1,
        ITransientService transientService2)
    {
        _transientService1 = transientService1;
        _transientService2 = transientService2;
    }
    [HttpGet]
    public IActionResult Get()
    {
        return Ok(new
        {
            Service1 = _transientService1.GetOperationId(),
            Service2 = _transientService2.GetOperationId()
        });
    }
}

Each time you hit the endpoint, you'll see different GUIDs for each service instance.

2. Scoped

A scoped service is created once per request. This is useful for services that maintain state within a single request.

public interface IScopedService
{
    Guid GetOperationId();
}

public class ScopedService : IScopedService
{
    private readonly Guid _operationId;
    public ScopedService()
    {
        _operationId = Guid.NewGuid();
    }
    public Guid GetOperationId() => _operationId;
}

Register the service in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IScopedService, ScopedService>();
}

Use the service in a controller

[ApiController]
[Route("api/[controller]")]
public class ExampleController : ControllerBase
{
    private readonly IScopedService _scopedService1;
    private readonly IScopedService _scopedService2;
    public ExampleController(IScopedService scopedService1, IScopedService scopedService2)
    {
        _scopedService1 = scopedService1;
        _scopedService2 = scopedService2;
    }
    [HttpGet]
    public IActionResult Get()
    {
        return Ok(new
        {
            Service1 = _scopedService1.GetOperationId(),
            Service2 = _scopedService2.GetOperationId()
        });
    }
}

Each time you hit the endpoint, you'll see the same GUID for both service instances within a single request, but different GUIDs across different requests.

3. Singleton

A singleton service is created once and shared across all requests. This is useful for stateless services that can be shared.

public interface ISingletonService
{
    Guid GetOperationId();
}

public class SingletonService : ISingletonService
{
    private readonly Guid _operationId;
    public SingletonService()
    {
        _operationId = Guid.NewGuid();
    }
    public Guid GetOperationId() => _operationId;
}

Register the service in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ISingletonService, SingletonService>();
}

Use the service in a controller

[ApiController]
[Route("api/[controller]")]
public class ExampleController : ControllerBase
{
    private readonly ISingletonService _singletonService1;
    private readonly ISingletonService _singletonService2;
    public ExampleController(ISingletonService singletonService1, ISingletonService singletonService2)
    {
        _singletonService1 = singletonService1;
        _singletonService2 = singletonService2;
    }
    [HttpGet]
    public IActionResult Get()
    {
        return Ok(new
        {
            Service1 = _singletonService1.GetOperationId(),
            Service2 = _singletonService2.GetOperationId()
        });
    }
}

Each time you hit the endpoint, you'll see the same GUID for both service instances across all requests.

Conclusion

Understanding and using the appropriate scope for your services in .NET Core is essential for efficient resource management and application performance. Transient services are suitable for lightweight and stateless operations, scoped services for maintaining state within a request, and singleton services for shared, stateless operations across the entire application. By carefully choosing the right scope, you can optimize your application's performance and maintainability.

I hope this article helps you understand the concept of scope in .NET Core and how to use it effectively. If you have any questions or need further clarification, feel free to ask!