Monday, August 31, 2026

Working with Windows Services in .NET — Complete Guide from Scratch to Production

A Windows Service is a long-running application that runs in the background without requiring a user to keep a console window open. In modern .NET, the recommended approach is to build a Worker Service using BackgroundService and configure it to run under the Windows Service infrastructure. Microsoft documents this approach for .NET 8 and later. (Microsoft Learn)

Below is a blog-ready article with a real-time example, architecture, code, deployment, logging, configuration, error handling, recovery, and troubleshooting.

Windows Services in .NET: Complete Guide with Real-Time Example

Introduction

In enterprise applications, we often have requirements where some processing must happen continuously in the background without a user manually starting the application.

For example:

  • Process pending orders

  • Generate invoices

  • Send notification emails

  • Read files from a folder

  • Synchronize data between systems

  • Process messages from a queue

  • Generate reports

  • Monitor application health

  • Clean up temporary files

  • Synchronize data with a third-party API

A normal console application is not ideal for these scenarios because someone has to manually start it.

A Windows Service solves this problem.

A Windows Service can start automatically when Windows starts and continue running in the background.

Modern .NET provides the Worker Service template and BackgroundService for implementing these applications.


1. What is a Windows Service?

A Windows Service is an application designed to run in the background under the control of the Windows Service Control Manager (SCM).

Unlike a normal desktop application:

Normal Console Application

User
  |
  v
Start Application
  |
  v
Console Window
  |
  v
Application Running

A Windows Service works more like:

Windows Operating System
        |
        v
Service Control Manager
        |
        v
Order Processing Service
        |
        v
Background Worker
        |
        +----> Database
        |
        +----> APIs
        |
        +----> File System
        |
        +----> Message Queue

The Service Control Manager can start, stop and monitor the service.


2. Why Do We Need Windows Services?

Imagine an e-commerce application.

Customers place orders throughout the day.

The application stores orders in SQL Server.

Instead of processing everything inside the Web API request, we can have a background service that periodically checks for pending orders.

Customer
   |
   v
Angular Application
   |
   v
.NET Web API
   |
   v
SQL Server
   |
   | Pending Orders
   v
Windows Service
   |
   +---- Process Order
   |
   +---- Generate Invoice
   |
   +---- Update Status
   |
   +---- Send Notification

This is a common enterprise architecture.


3. Real-Time Example

Let's build a real-world application called:

Order Processing Windows Service

Our requirement is:

Every 30 seconds, the Windows Service should check SQL Server for pending orders and process them.

The workflow will be:

SQL Server
    |
    | Pending Orders
    v
Windows Service
    |
    v
Read Order
    |
    v
Process Order
    |
    v
Generate Invoice
    |
    v
Update Order Status
    |
    v
Log Result

4. Worker Service vs Windows Service

These two terms are often confused.

Worker Service

A Worker Service is a .NET application designed for long-running background processing.

Windows Service

A Windows Service is the way the operating system hosts and manages a background application.

Therefore:

Worker Service
      +
UseWindowsService()
      |
      v
Windows Service

The modern .NET approach is to create a Worker Service and configure it to run as a Windows Service.

Microsoft recommends using the Worker Service template with BackgroundService for this scenario. (Microsoft Learn)


5. Prerequisites

You need:

  • Windows OS

  • .NET SDK

  • Visual Studio or VS Code

  • SQL Server if using the database example

  • Administrator privileges for installing the Windows Service

Microsoft's current Windows Service documentation uses .NET 8 or later as the prerequisite baseline. (Microsoft Learn)


6. Create the Worker Service

Using the .NET CLI:

dotnet new worker -n OrderProcessingService

Move into the project:

cd OrderProcessingService

Run the application:

dotnet run

The Worker Service template creates a background worker application.

The template can also be created from Visual Studio by selecting:

Create a new project
        |
        v
Worker Service

7. Project Structure

Our project can look like this:

OrderProcessingService
│
├── Program.cs
├── Worker.cs
├── appsettings.json
│
├── Models
│   └── Order.cs
│
├── Services
│   ├── IOrderProcessor.cs
│   └── OrderProcessor.cs
│
└── Data
    └── OrderRepository.cs

A good architecture separates:

Worker
  |
  v
Business Service
  |
  v
Repository
  |
  v
Database

The Worker should not contain all business logic.


8. Install Windows Service Package

Install:

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

This package provides Windows Service integration for the .NET hosting infrastructure. (NuGet)


9. Understanding BackgroundService

The main class of our Worker Service will inherit from:

BackgroundService

Example:

public class Worker : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Background processing

            await Task.Delay(
                TimeSpan.FromSeconds(30),
                stoppingToken);
        }
    }
}

The important method is:

ExecuteAsync()

This is where the background processing happens.


10. Understanding CancellationToken

A Windows Service must be able to stop gracefully.

Suppose Windows sends a stop command.

We don't want the application to suddenly terminate in the middle of an operation.

Therefore:

CancellationToken stoppingToken

is provided.

We can check:

while (!stoppingToken.IsCancellationRequested)

This means:

Continue processing until Windows tells the service to stop.


11. Program.cs

Now configure our application.

using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "Order Processing Service";
});

builder.Services.AddHostedService<Worker>();

var host = builder.Build();

host.Run();

The important line is:

builder.Services.AddWindowsService();

This configures the application to work with Windows Service lifetime management. Microsoft's current documentation uses this approach with Host.CreateApplicationBuilder. (Microsoft Learn)


12. Worker.cs

Create our worker:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Order Processing Service started.");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                _logger.LogInformation(
                    "Checking for pending orders.");

                await ProcessOrders(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(
                    ex,
                    "Error while processing orders.");
            }

            await Task.Delay(
                TimeSpan.FromSeconds(30),
                stoppingToken);
        }

        _logger.LogInformation(
            "Order Processing Service stopped.");
    }

    private async Task ProcessOrders(
        CancellationToken cancellationToken)
    {
        // Order processing logic

        await Task.CompletedTask;
    }
}

13. Why Use Task.Delay?

Suppose we want to execute our process every 30 seconds.

We can use:

await Task.Delay(
    TimeSpan.FromSeconds(30),
    stoppingToken);

The flow becomes:

Service Starts
      |
      v
Process Orders
      |
      v
Wait 30 Seconds
      |
      v
Process Orders
      |
      v
Wait 30 Seconds
      |
      v
Continue...

14. Don't Use Thread.Sleep

Avoid:

Thread.Sleep(30000);

Prefer:

await Task.Delay(
    TimeSpan.FromSeconds(30),
    stoppingToken);

Why?

Thread.Sleep() blocks the thread.

Task.Delay() allows asynchronous waiting and can respond to cancellation.


15. Create Order Model

Create:

Models/Order.cs
namespace OrderProcessingService.Models;

public class Order
{
    public int Id { get; set; }

    public string OrderNumber { get; set; } = string.Empty;

    public decimal Amount { get; set; }

    public string Status { get; set; } = string.Empty;

    public DateTime CreatedDate { get; set; }
}

16. Database Table

Suppose we have this SQL Server table:

CREATE TABLE Orders
(
    Id INT IDENTITY PRIMARY KEY,

    OrderNumber VARCHAR(50) NOT NULL,

    Amount DECIMAL(18,2) NOT NULL,

    Status VARCHAR(20) NOT NULL,

    CreatedDate DATETIME2 NOT NULL
);

Insert sample records:

INSERT INTO Orders
(
    OrderNumber,
    Amount,
    Status,
    CreatedDate
)
VALUES
(
    'ORD1001',
    2500,
    'Pending',
    GETDATE()
);

INSERT INTO Orders
(
    OrderNumber,
    Amount,
    Status,
    CreatedDate
)
VALUES
(
    'ORD1002',
    3500,
    'Pending',
    GETDATE()
);

17. Create Repository

Create:

Data/OrderRepository.cs

For demonstration, we can use ADO.NET.

using Microsoft.Data.SqlClient;
using OrderProcessingService.Models;

public class OrderRepository
{
    private readonly string _connectionString;

    public OrderRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    public async Task<List<Order>> GetPendingOrdersAsync(
        CancellationToken cancellationToken)
    {
        var orders = new List<Order>();

        using var connection =
            new SqlConnection(_connectionString);

        await connection.OpenAsync(cancellationToken);

        var command = new SqlCommand(
            """
            SELECT TOP 10
                   Id,
                   OrderNumber,
                   Amount,
                   Status,
                   CreatedDate
            FROM Orders
            WHERE Status = 'Pending'
            ORDER BY Id
            """,
            connection);

        using var reader =
            await command.ExecuteReaderAsync(
                cancellationToken);

        while (await reader.ReadAsync(cancellationToken))
        {
            orders.Add(new Order
            {
                Id = reader.GetInt32(0),
                OrderNumber = reader.GetString(1),
                Amount = reader.GetDecimal(2),
                Status = reader.GetString(3),
                CreatedDate = reader.GetDateTime(4)
            });
        }

        return orders;
    }
}

18. Register Repository Using Dependency Injection

Instead of creating the repository manually inside the Worker, use Dependency Injection.

Example:

builder.Services.AddSingleton<OrderRepository>();

However, for production applications, it is usually better to register database-related services according to their actual lifetime and dependency behavior.

For example:

builder.Services.AddScoped<IOrderProcessor, OrderProcessor>();

For scoped services inside a BackgroundService, create an explicit scope using IServiceScopeFactory.


19. Business Service

Create:

Services/IOrderProcessor.cs
public interface IOrderProcessor
{
    Task ProcessAsync(
        CancellationToken cancellationToken);
}

Implementation:

public class OrderProcessor : IOrderProcessor
{
    private readonly OrderRepository _repository;
    private readonly ILogger<OrderProcessor> _logger;

    public OrderProcessor(
        OrderRepository repository,
        ILogger<OrderProcessor> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task ProcessAsync(
        CancellationToken cancellationToken)
    {
        var orders =
            await _repository.GetPendingOrdersAsync(
                cancellationToken);

        foreach (var order in orders)
        {
            _logger.LogInformation(
                "Processing order {OrderNumber}",
                order.OrderNumber);

            // Business logic

            _logger.LogInformation(
                "Order {OrderNumber} processed successfully.",
                order.OrderNumber);
        }
    }
}

20. Worker with Dependency Injection

Now our Worker becomes cleaner.

public class Worker : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<Worker> _logger;

    public Worker(
        IServiceScopeFactory scopeFactory,
        ILogger<Worker> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Order Processing Service started.");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope =
                    _scopeFactory.CreateScope();

                var processor =
                    scope.ServiceProvider
                        .GetRequiredService<IOrderProcessor>();

                await processor.ProcessAsync(
                    stoppingToken);
            }
            catch (OperationCanceledException)
                when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(
                    ex,
                    "Unexpected error.");
            }

            await Task.Delay(
                TimeSpan.FromSeconds(30),
                stoppingToken);
        }

        _logger.LogInformation(
            "Order Processing Service stopped.");
    }
}

This gives us a clean architecture:

Worker
  |
  v
IOrderProcessor
  |
  v
OrderProcessor
  |
  v
OrderRepository
  |
  v
SQL Server

21. appsettings.json

Configuration should not be hard-coded.

Create:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=OrderDb;Trusted_Connection=True;TrustServerCertificate=True"
  },

  "WorkerSettings": {
    "IntervalSeconds": 30,
    "BatchSize": 10
  },

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

22. Using Configuration

Create:

public class WorkerSettings
{
    public int IntervalSeconds { get; set; }

    public int BatchSize { get; set; }
}

Register it:

builder.Services.Configure<WorkerSettings>(
    builder.Configuration.GetSection("WorkerSettings"));

Now the interval can be changed without modifying the code.


23. Complete Program.cs

Our final Program.cs can look like:

using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "Order Processing Service";
});

builder.Services.Configure<WorkerSettings>(
    builder.Configuration.GetSection("WorkerSettings"));

var connectionString =
    builder.Configuration.GetConnectionString(
        "DefaultConnection");

builder.Services.AddSingleton(
    new OrderRepository(connectionString!));

builder.Services.AddScoped<IOrderProcessor, OrderProcessor>();

builder.Services.AddHostedService<Worker>();

var host = builder.Build();

host.Run();

24. Logging

Logging is extremely important for Windows Services because there is no console window when the service is running in production.

We can use:

_logger.LogInformation(
    "Order {OrderNumber} processed.",
    order.OrderNumber);

Other levels include:

_logger.LogDebug("Debug information");

_logger.LogInformation("Information");

_logger.LogWarning("Warning");

_logger.LogError("Error");

_logger.LogCritical("Critical failure");

25. Windows Event Viewer

When running as a Windows Service, logs can be written to the Windows Event Log.

Microsoft's Windows Service hosting integration supports Event Log logging, and UseWindowsService/AddWindowsService configures Windows Service behavior and Event Log integration. (Microsoft Learn)

You can open:

Start
  |
  v
Event Viewer
  |
  v
Windows Logs
  |
  v
Application

Then search for events generated by your application.


26. Important Difference: Console vs Windows Service

During development:

dotnet run

The application runs as a console process.

After installation:

Windows
   |
   v
Service Control Manager
   |
   v
Order Processing Service
   |
   v
Worker

The same application can therefore be useful both locally and as a Windows Service.


27. Publish the Application

Before installing the service, publish the application.

For a Windows x64 deployment:

dotnet publish -c Release -r win-x64 --self-contained true

You can also publish as a single executable.

Microsoft recommends publishing a Worker Service as a single-file executable for Windows Service deployment because it reduces deployment-file complexity. (Microsoft Learn)

Example:

dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true

The published application will be under a path similar to:

bin\Release\net9.0\win-x64\publish\

The exact target framework depends on the .NET version used by your project.


28. Installing the Windows Service

Open:

PowerShell

or:

Command Prompt

as Administrator.

Navigate to the published executable.

Then:

sc.exe create "Order Processing Service" binPath= "C:\Services\OrderProcessingService\OrderProcessingService.exe"

If successful, Windows reports:

[SC] CreateService SUCCESS

Microsoft documents sc.exe create as the native Service Control Manager approach for creating the service. (Microsoft Learn)


29. Start the Service

Run:

sc.exe start "Order Processing Service"

Or open:

Services

Find:

Order Processing Service

Then:

Right Click
    |
    v
Start

30. Service Lifecycle

The complete lifecycle looks like:

Windows Boot
     |
     v
Service Control Manager
     |
     v
Start Service
     |
     v
.NET Host
     |
     v
BackgroundService
     |
     v
ExecuteAsync()
     |
     v
Process Orders
     |
     v
Wait
     |
     +--------+
              |
              v
        Process Again

When Windows stops the service:

Windows
   |
   v
Stop Request
   |
   v
CancellationToken
   |
   v
ExecuteAsync exits
   |
   v
Host shuts down
   |
   v
Service stopped

31. Stop the Service

sc.exe stop "Order Processing Service"

Or use:

Services
  |
  v
Order Processing Service
  |
  v
Stop

32. Delete the Service

If you want to completely remove it:

sc.exe stop "Order Processing Service"

sc.exe delete "Order Processing Service"

Microsoft notes that a service should be stopped before deleting it. (Microsoft Learn)


33. Configure Automatic Startup

A production service normally should start automatically.

Use:

sc.exe config "Order Processing Service" start= auto

Now Windows can start the service automatically during system startup.


34. Configure Service Recovery

One of the biggest advantages of Windows Services is recovery configuration.

Imagine:

Order Processing Service
        |
        v
Unexpected Error
        |
        v
Process Terminates

We want:

Service Failure
      |
      v
Windows Service Manager
      |
      v
Restart Service

Microsoft provides sc.exe failure for configuring service recovery actions. (Microsoft Learn)

Example:

sc.exe failure "Order Processing Service" reset= 86400 actions= restart/60000/restart/60000/run/1000

This can configure restart actions after failures.


35. Why Recovery Is Important

Consider a production server:

2:00 AM
   |
   v
Service crashes
   |
   v
Windows detects failure
   |
   v
Service automatically restarts
   |
   v
Processing continues

Without recovery:

Service crashes
     |
     v
Processing stops
     |
     v
Manual intervention required

With recovery:

Service crashes
     |
     v
Automatic restart
     |
     v
Processing continues

36. Handling Exceptions

Never allow an unexpected exception to bring down the entire worker loop unnecessarily.

Bad:

while (true)
{
    await ProcessOrders();
}

Better:

while (!stoppingToken.IsCancellationRequested)
{
    try
    {
        await ProcessOrders(stoppingToken);
    }
    catch (Exception ex)
    {
        _logger.LogError(
            ex,
            "Error processing orders.");
    }

    await Task.Delay(
        TimeSpan.FromSeconds(30),
        stoppingToken);
}

However, exception handling should be designed carefully. Some failures indicate that the application should stop rather than endlessly retry.


37. Graceful Shutdown

Suppose an order is currently being processed:

Processing Order 1001
        |
        |
Windows Stop Request
        |
        v
CancellationToken

The service should stop accepting new work and allow the current operation to finish when appropriate.

Use:

CancellationToken

throughout the call chain:

Worker
  |
  v
Processor
  |
  v
Repository
  |
  v
Database

For example:

await connection.OpenAsync(
    cancellationToken);

and:

await command.ExecuteReaderAsync(
    cancellationToken);

38. Avoid Long Blocking Operations

Avoid:

Thread.Sleep(...)

Avoid synchronous network calls when asynchronous APIs are available.

Prefer:

await httpClient.GetAsync(
    url,
    cancellationToken);

Prefer asynchronous database operations:

await command.ExecuteNonQueryAsync(
    cancellationToken);

This makes the service more responsive and easier to shut down gracefully.


39. Calling a Third-Party API

Suppose after processing an order we need to notify an external payment service.

Use HttpClient through Dependency Injection.

builder.Services.AddHttpClient(
    "PaymentApi",
    client =>
    {
        client.BaseAddress =
            new Uri("https://api.example.com/");
    });

Then:

public class PaymentService
{
    private readonly IHttpClientFactory _httpClientFactory;

    public PaymentService(
        IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task NotifyPaymentAsync(
        int orderId,
        CancellationToken cancellationToken)
    {
        var client =
            _httpClientFactory.CreateClient("PaymentApi");

        await client.PostAsJsonAsync(
            "payments/process",
            new
            {
                OrderId = orderId
            },
            cancellationToken);
    }
}

40. Real Production Architecture

A production implementation could look like:

                 ┌─────────────────────┐
                 │      SQL Server     │
                 │                     │
                 │ Pending Orders      │
                 └──────────┬──────────┘
                            │
                            ▼
                 ┌─────────────────────┐
                 │  Windows Service    │
                 │                     │
                 │ BackgroundService   │
                 └──────────┬──────────┘
                            │
                            ▼
                 ┌─────────────────────┐
                 │ Order Processor     │
                 │                     │
                 │ Business Logic      │
                 └──────────┬──────────┘
                            │
             ┌──────────────┼──────────────┐
             ▼              ▼              ▼
       SQL Server       Payment API    Notification API
             │              │              │
             └──────────────┼──────────────┘
                            ▼
                       Logging
                            │
                            ▼
                     Event Viewer

41. Windows Service vs Web API

These applications solve different problems.

FeatureWeb APIWindows Service
User requestYesNo
HTTP endpointYesNot required
Long-running background workNot idealExcellent
Runs continuouslyUsually hosted continuouslyYes
TriggerHTTP requestTimer/event/message
UI requiredNoNo
Windows Service supportPossibleNative scenario
Background processingLimited/specializedExcellent

A Web API is usually request-driven.

A Windows Service is usually background-driven.


42. Windows Service vs BackgroundService

They are not exactly the same thing.

BackgroundService
       |
       +---- Console Application
       |
       +---- Windows Service
       |
       +---- Container
       |
       +---- Other Host

BackgroundService is an abstraction for implementing long-running hosted background work.

Windows Service hosting is one way to host that worker.


43. Timer-Based Processing

For periodic work, another pattern is using PeriodicTimer.

Example:

protected override async Task ExecuteAsync(
    CancellationToken stoppingToken)
{
    using var timer =
        new PeriodicTimer(
            TimeSpan.FromSeconds(30));

    while (await timer.WaitForNextTickAsync(
        stoppingToken))
    {
        await ProcessOrders(
            stoppingToken);
    }
}

This can make periodic processing easier to read.


44. Important Production Consideration: Duplicate Processing

Suppose:

Worker
   |
   v
Gets Order 1001
   |
   v
Starts Processing

Before updating the status, the service crashes.

After restart:

Worker
   |
   v
Gets Order 1001 again

Now the same order could be processed twice.

This is a major production concern.

We need an idempotent processing strategy.


45. Use Status Transitions

Instead of:

Pending
  |
  v
Processed

use:

Pending
   |
   v
Processing
   |
   v
Completed

If something fails:

Processing
     |
     v
Failed

Example:

Pending
   |
   v
Processing
   |
   +------> Failed
   |
   v
Completed

This allows us to understand exactly where the order is.


46. Database Transaction

Critical operations can use transactions.

Conceptually:

BEGIN TRANSACTION

Get Pending Order

Change Status = Processing

Perform Database Operations

Change Status = Completed

COMMIT

If an operation fails:

ROLLBACK

However, transactions should not normally be held open across slow external API calls. For distributed workflows, patterns such as idempotency, outbox/inbox, queues, and Saga may be more appropriate.


47. Windows Service + Message Queue

In a larger architecture, instead of polling SQL Server:

SQL Server
   |
   v
Windows Service

we might use:

Web API
   |
   v
Azure Service Bus / RabbitMQ
   |
   v
Windows Service
   |
   v
Order Processor

The Worker waits for messages.

This is often better when work should be processed asynchronously and reliably.


48. Windows Service + Azure Service Bus

For example:

Customer
   |
   v
Web API
   |
   v
Azure Service Bus
   |
   v
Windows Service
   |
   v
Order Processing

The service can consume messages continuously.

Conceptually:

while (!stoppingToken.IsCancellationRequested)
{
    var message =
        await ReceiveMessageAsync(
            stoppingToken);

    await ProcessMessageAsync(
        message,
        stoppingToken);
}

For enterprise systems, this approach can provide better decoupling than repeatedly querying the database.


49. Health Monitoring

Production Windows Services should be monitored.

Useful information includes:

Service Status
Last Successful Processing
Last Failure
Number of Records Processed
Processing Duration
Database Connectivity
External API Availability

For example:

Order Processing Service

Status: Running

Orders Processed: 15,230

Last Successful Run:
2026-08-31 12:15:00

Last Error:
None

Average Processing Time:
1.8 seconds

50. Configuration by Environment

Avoid hard-coding production settings.

Use:

appsettings.json
appsettings.Development.json
appsettings.Production.json

Example:

{
  "WorkerSettings": {
    "IntervalSeconds": 30
  }
}

Development:

{
  "WorkerSettings": {
    "IntervalSeconds": 10
  }
}

Production:

{
  "WorkerSettings": {
    "IntervalSeconds": 60
  }
}

51. Security

Do not store passwords directly inside source code.

Bad:

var connectionString =
    "Server=...;User Id=admin;Password=12345";

Better approaches include:

  • Windows authentication where appropriate

  • Environment-specific configuration

  • Secret management

  • Azure Key Vault for Azure-hosted workloads

  • Restricted service accounts

The service should run with only the permissions it actually needs.

Avoid giving unnecessary administrator privileges.


52. Service Account

A Windows Service runs under an account.

Common options include:

Local System
Local Service
Network Service
Custom Service Account

For production applications, use an appropriately restricted service identity rather than automatically granting broad privileges.

The identity should have only the permissions required for:

Database
File System
Network
APIs
Certificates
Logs

53. File Access

If your service processes files:

C:\Input
C:\Output
C:\Archive

make sure the Windows Service account has appropriate permissions.

A common mistake is:

Console Application
     |
     v
Works perfectly

but:

Windows Service
     |
     v
Access Denied

Why?

Because the console application and Windows Service may be running under different user accounts.


54. Current Directory Problem

When running interactively, developers sometimes use:

Directory.GetCurrentDirectory()

But Windows Services may have a different working directory.

Prefer application-relative paths based on:

AppContext.BaseDirectory

Microsoft's Windows Service hosting integration sets the content root appropriately when running as a Windows Service. (Microsoft Learn)


55. Deployment Process

A typical deployment process is:

Developer
   |
   v
Git Repository
   |
   v
CI/CD Pipeline
   |
   v
dotnet build
   |
   v
dotnet test
   |
   v
dotnet publish
   |
   v
Deployment Server
   |
   v
Stop Service
   |
   v
Copy New Version
   |
   v
Start Service
   |
   v
Verify Logs

For enterprise applications, this process can be automated using Azure DevOps or another CI/CD platform.


56. Updating the Service

Suppose version 1 is installed:

OrderProcessingService v1

You release:

OrderProcessingService v2

Typical deployment:

sc.exe stop "Order Processing Service"

Deploy the new files.

Then:

sc.exe start "Order Processing Service"

Always plan deployment carefully if the service is processing critical work.


57. Troubleshooting

Problem 1: Service doesn't start

Check:

Event Viewer

Also verify:

Executable path
Service account permissions
Configuration
Connection strings
Required files
.NET runtime

Problem 2: Service starts and immediately stops

Possible causes:

Unhandled exception
Invalid configuration
Missing dependency
Database connection failure
Invalid executable
Startup exception

Check Event Viewer and application logs.


Problem 3: Works with dotnet run but not as a service

Common reasons:

Different service account
File permission issue
Different working directory
Environment configuration
Missing configuration file
Database authentication
Network permissions

Problem 4: Database connection fails

Check:

SQL Server availability
Connection string
Authentication
Firewall
Service account permissions
Database permissions

Problem 5: Service keeps restarting

Check:

Event Viewer
Application logs
Service recovery configuration
Unhandled exceptions
Memory/CPU issues
External dependency failures

58. Complete Architecture

A mature implementation might look like:

                  ┌─────────────────────┐
                  │ Windows Server      │
                  │                     │
                  │ Service Control     │
                  │ Manager             │
                  └──────────┬──────────┘
                             │
                             ▼
                  ┌─────────────────────┐
                  │ Order Worker        │
                  │                     │
                  │ BackgroundService   │
                  └──────────┬──────────┘
                             │
                             ▼
                  ┌─────────────────────┐
                  │ Order Processor      │
                  │                     │
                  │ Business Rules      │
                  └───────┬───────┬─────┘
                          │       │
                ┌─────────┘       └─────────┐
                ▼                           ▼
        ┌──────────────┐             ┌──────────────┐
        │ SQL Server   │             │ External API │
        └──────────────┘             └──────────────┘
                          │
                          ▼
                    ┌───────────┐
                    │ Event Log │
                    └───────────┘

59. End-to-End Flow

Let's summarize the entire application.

Step 1 — Windows starts

Windows Server

Step 2 — Service Control Manager starts the service

SCM
 |
 v
Order Processing Service

Step 3 — .NET Host starts

Host
 |
 v
Dependency Injection
 |
 v
BackgroundService

Step 4 — Worker starts

Worker.ExecuteAsync()

Step 5 — Worker retrieves orders

SQL Server
 |
 v
Pending Orders

Step 6 — Business logic executes

OrderProcessor

Step 7 — Database gets updated

Pending
   |
   v
Processing
   |
   v
Completed

Step 8 — Logging occurs

Event Log

Step 9 — Worker waits

30 seconds

Step 10 — Processing starts again

Process
  |
  v
Wait
  |
  v
Process
  |
  v
Wait

Step 11 — Windows sends stop signal

CancellationToken

Step 12 — Worker exits gracefully

ExecuteAsync()
     |
     v
Host Shutdown
     |
     v
Service Stopped

60. Complete Worker Example

Here is a simplified final version:

using Microsoft.Extensions.Hosting;

public class Worker : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<Worker> _logger;

    public Worker(
        IServiceScopeFactory scopeFactory,
        ILogger<Worker> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Order Processing Service started.");

        using var timer =
            new PeriodicTimer(
                TimeSpan.FromSeconds(30));

        try
        {
            while (await timer.WaitForNextTickAsync(
                stoppingToken))
            {
                try
                {
                    using var scope =
                        _scopeFactory.CreateScope();

                    var processor =
                        scope.ServiceProvider
                            .GetRequiredService<IOrderProcessor>();

                    await processor.ProcessAsync(
                        stoppingToken);
                }
                catch (OperationCanceledException)
                    when (stoppingToken.IsCancellationRequested)
                {
                    break;
                }
                catch (Exception ex)
                {
                    _logger.LogError(
                        ex,
                        "Error occurred while processing orders.");
                }
            }
        }
        catch (OperationCanceledException)
            when (stoppingToken.IsCancellationRequested)
        {
            // Expected during shutdown.
        }

        _logger.LogInformation(
            "Order Processing Service stopped.");
    }
}

61. Complete Program Example

using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "Order Processing Service";
});

builder.Services.Configure<WorkerSettings>(
    builder.Configuration.GetSection("WorkerSettings"));

var connectionString =
    builder.Configuration.GetConnectionString(
        "DefaultConnection");

builder.Services.AddSingleton(
    new OrderRepository(connectionString!));

builder.Services.AddScoped<IOrderProcessor, OrderProcessor>();

builder.Services.AddHostedService<Worker>();

var host = builder.Build();

host.Run();

62. Commands Cheat Sheet

Create project

dotnet new worker -n OrderProcessingService

Add Windows Service support

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

Build

dotnet build

Run locally

dotnet run

Publish

dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true

Create service

sc.exe create "Order Processing Service" binPath= "C:\Services\OrderProcessingService\OrderProcessingService.exe"

Configure automatic startup

sc.exe config "Order Processing Service" start= auto

Start

sc.exe start "Order Processing Service"

Stop

sc.exe stop "Order Processing Service"

Delete

sc.exe delete "Order Processing Service"

Check failure configuration

sc.exe qfailure "Order Processing Service"

63. Best Practices

For production Windows Services, follow these practices:

  1. Use BackgroundService.

  2. Use Dependency Injection.

  3. Use asynchronous APIs.

  4. Pass CancellationToken throughout the processing pipeline.

  5. Use structured logging.

  6. Don't hard-code connection strings.

  7. Use secure secret management.

  8. Run using a least-privilege service account.

  9. Configure service recovery.

  10. Make processing idempotent.

  11. Use database transactions where appropriate.

  12. Avoid processing the same record twice.

  13. Monitor service health.

  14. Monitor CPU and memory.

  15. Keep the Worker class thin.

  16. Put business logic into separate services.

  17. Use configuration for intervals and batch sizes.

  18. Test the application as both a console application and Windows Service.

  19. Automate deployment through CI/CD where practical.

  20. Use a message broker when continuous database polling is no longer appropriate.


64. When Should You Use a Windows Service?

Windows Services are particularly useful when:

Continuous Background Processing
          +
Windows Server Environment
          +
No User Interaction Required

Examples:

File Processing

Input Folder
     |
     v
Windows Service
     |
     v
Validate File
     |
     v
Process File
     |
     v
Archive File

Order Processing

Database
   |
   v
Windows Service
   |
   v
Process Orders

Data Synchronization

System A
   |
   v
Windows Service
   |
   v
System B

Scheduled Reports

Windows Service
      |
      v
Generate Report
      |
      v
Save PDF
      |
      v
Send Notification

65. When Should You NOT Use a Windows Service?

A Windows Service may not be the best choice when:

  • The workload is already event-driven through a managed cloud messaging platform.

  • You need massive horizontal scaling.

  • The workload is better suited to serverless functions.

  • The application must expose HTTP endpoints as its primary responsibility.

  • The environment is Linux-only.

  • A managed cloud service can perform the same task more reliably.

For cloud-native systems, alternatives can include:

Azure Functions
Azure Container Apps
AKS
Azure Service Bus
Cloud-hosted Worker Services

The right choice depends on the workload and operational requirements.


66. Interview Questions

Q1. What is a Windows Service?

A Windows Service is a background application managed by the Windows Service Control Manager.

Q2. What is BackgroundService?

BackgroundService is a .NET base class used to implement long-running background tasks.

Q3. How do you convert a Worker Service into a Windows Service?

Install:

Microsoft.Extensions.Hosting.WindowsServices

and configure:

builder.Services.AddWindowsService();

Q4. What is ExecuteAsync?

It is the main asynchronous method where the background work is implemented.

Q5. Why use CancellationToken?

It allows the worker to respond to shutdown requests gracefully.

Q6. How do you install a Windows Service?

Using:

sc.exe create

Q7. How do you start it?

sc.exe start

Q8. How do you stop it?

sc.exe stop

Q9. How do you remove it?

sc.exe delete

Q10. Where can you check Windows Service errors?

Use:

Event Viewer
→ Windows Logs
→ Application

Q11. How do you automatically restart a failed service?

Configure Windows Service recovery actions using Service Control Manager settings or sc.exe failure.

Q12. How do you avoid duplicate processing?

Use techniques such as:

Idempotency
Status transitions
Database constraints
Transactions
Outbox/Inbox patterns
Message deduplication

depending on the architecture.


67. Final Takeaway

A modern .NET Windows Service is not simply a program containing an infinite loop.

A production-quality implementation should have:

                 Windows Service
                       |
                       v
                BackgroundService
                       |
                       v
                Dependency Injection
                       |
                       v
                 Business Service
                       |
             +---------+---------+
             |                   |
             v                   v
         Database            External API
             |                   |
             +---------+---------+
                       |
                       v
                    Logging
                       |
                       v
                 Monitoring

The most important concepts to remember are:

Worker Service
      ↓
BackgroundService
      ↓
ExecuteAsync()
      ↓
CancellationToken
      ↓
Dependency Injection
      ↓
Business Processing
      ↓
Logging
      ↓
Publish
      ↓
Windows Service
      ↓
Service Control Manager
      ↓
Recovery + Monitoring

This architecture provides a clean foundation for building background processing applications such as order processors, file processors, synchronization services, scheduled jobs, notification services, and enterprise integration services.


Conclusion

Windows Services continue to be useful for long-running background workloads on Windows servers.

With modern .NET, the preferred approach is to build the application using the Worker Service/BackgroundService model and then integrate it with Windows Service hosting.

The important distinction is:

BackgroundService implements the background work, while Windows Service hosting allows Windows to manage the application's lifecycle.

Once this foundation is understood, the same Worker Service concepts can be extended to SQL Server processing, REST APIs, Azure Service Bus, RabbitMQ, file processing, scheduled jobs, monitoring, and other enterprise workloads.

Official Microsoft references

If you want, I can next turn this into a professional blog thumbnail + architecture diagram, or convert the complete article into Telugu.

Friday, August 21, 2026

Service Life Cycle in .NET Core Application

 


Transient vs Scoped vs Singleton with Real-Time E-Commerce Example

Dependency Injection (DI) is one of the most important concepts in ASP.NET Core. It helps us create loosely coupled, maintainable, testable, and scalable applications.

One of the most frequently asked .NET interview questions is:

What is Service Lifetime in .NET Core? Explain Transient, Scoped, and Singleton with a real-time example.

To understand this properly, we need to understand what happens to a service from the moment it is requested until the moment it is destroyed.


1. What is Service Life Cycle?

A Service Life Cycle defines:

  • When an object is created

  • How long that object lives

  • Whether the same object is reused

  • When the object is destroyed

In ASP.NET Core, the built-in Dependency Injection container provides three primary service lifetimes:

  1. Transient

  2. Scoped

  3. Singleton

The basic idea is:

Application Starts
       |
       v
DI Container Created
       |
       v
Request Comes
       |
       v
Controller / Service Requests Dependency
       |
       v
DI Container Checks Service Lifetime
       |
       +-------------------+
       |                   |
   Transient           Scoped
       |                   |
New instance          One per Request
       |                   |
       +---------+---------+
                 |
              Singleton
                 |
          One per Application

2. Dependency Injection in ASP.NET Core

Suppose we have an e-commerce application.

A request comes to:

GET /api/orders/1001

The request reaches the controller:

public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;

    public OrdersController(IOrderService orderService)
    {
        _orderService = orderService;
    }
}

ASP.NET Core needs to create IOrderService.

It looks into the DI container and asks:

How is IOrderService registered?
What is its lifetime?
Should I create a new instance?
Can I reuse an existing instance?

For example:

builder.Services.AddScoped<IOrderService, OrderService>();

This tells ASP.NET Core:

Create one OrderService instance for each HTTP request and reuse it throughout that request.


3. The Three Service Lifetimes

The three important registrations are:

services.AddTransient<IService, Service>();

services.AddScoped<IService, Service>();

services.AddSingleton<IService, Service>();

Their behavior is different.

LifetimeInstance CreationLifetime
TransientNew instance every time requestedVery short
ScopedOne instance per HTTP requestRequest lifetime
SingletonOne instance for applicationApplication lifetime

Let's understand each one with a real-time example.


4. Transient Lifetime

Transient means:

A new object is created every time the service is requested from the DI container.

Registration:

builder.Services.AddTransient<IEmailService, EmailService>();

Suppose:

public interface IEmailService
{
    void SendEmail();
}

Implementation:

public class EmailService : IEmailService
{
    public EmailService()
    {
        Console.WriteLine("EmailService Created");
    }

    public void SendEmail()
    {
        Console.WriteLine("Email Sent");
    }
}

Now imagine:

public class OrderService
{
    private readonly IEmailService _emailService;

    public OrderService(IEmailService emailService)
    {
        _emailService = emailService;
    }
}

Another service also requests:

public class NotificationService
{
    private readonly IEmailService _emailService;

    public NotificationService(IEmailService emailService)
    {
        _emailService = emailService;
    }
}

Because EmailService is transient:

OrderService
     |
     +---- EmailService Instance #1

NotificationService
     |
     +---- EmailService Instance #2

Two different objects are created.


5. Real-Time Use Cases for Transient

Transient is suitable for lightweight, stateless services.

Examples:

Email Formatter
Message Formatter
DTO Mapper
Validation Service
Small Calculation Service
Data Transformation Service

For example:

builder.Services.AddTransient<IPriceCalculator, PriceCalculator>();

Every time IPriceCalculator is requested, a new instance can be created.


6. Scoped Lifetime

Scoped is probably the most commonly used lifetime in ASP.NET Core Web API applications.

Registration:

builder.Services.AddScoped<IOrderService, OrderService>();

Scoped means:

One instance is created for a particular scope. In ASP.NET Core Web API, the scope normally corresponds to one HTTP request.

Consider:

POST /api/orders

The request enters ASP.NET Core.

ASP.NET Core creates a request scope.

HTTP Request
     |
     v
Request Scope Created
     |
     +---- OrderService
     |
     +---- CustomerService
     |
     +---- Repository
     |
     +---- DbContext
     |
     v
Request Completed
     |
     v
Request Scope Destroyed

All scoped services requested during that request can share their scoped instance.


7. Real-Time Example – E-Commerce Order

Consider an e-commerce application.

The request:

POST /api/orders

requires:

OrdersController
       |
       v
OrderService
       |
       v
OrderRepository
       |
       v
ApplicationDbContext

Registration:

builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<ApplicationDbContext>();

During Request #1:

Request #1

OrderService       ---> Instance A
OrderRepository    ---> Instance A
DbContext          ---> Instance A

Another request arrives:

POST /api/orders

Request #2 gets different scoped objects:

Request #2

OrderService       ---> Instance B
OrderRepository    ---> Instance B
DbContext           ---> Instance B

Therefore:

Request #1
   |
   +-- OrderService A
   +-- Repository A
   +-- DbContext A


Request #2
   |
   +-- OrderService B
   +-- Repository B
   +-- DbContext B

This isolation is one of the major reasons why DbContext is normally registered as Scoped.


8. Why DbContext is Usually Scoped

Consider an order transaction:

Create Order
     |
     +-- Insert Order
     |
     +-- Insert Order Items
     |
     +-- Update Inventory
     |
     +-- SaveChanges()

The same DbContext can track entities involved in that request.

For example:

public class OrderService
{
    private readonly ApplicationDbContext _context;

    public OrderService(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task CreateOrder(Order order)
    {
        _context.Orders.Add(order);

        await _context.SaveChangesAsync();
    }
}

Using a scoped context helps keep the database unit of work associated with the request.


9. Singleton Lifetime

Singleton means:

Only one instance of the service is created for the application's DI container lifetime, and that instance is reused wherever the service is requested.

Registration:

builder.Services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();

Conceptually:

Application Starts
       |
       v
Singleton Instance Created
       |
       +-----------------------+
       |                       |
Request 1                  Request 2
       |                       |
       +----------+------------+
                  |
          Same Singleton

The same object can be reused across requests.


10. Real-Time Singleton Example

Suppose our application has application-wide configuration.

public interface IApplicationConfiguration
{
    string ApplicationName { get; }
}

Implementation:

public class ApplicationConfiguration : IApplicationConfiguration
{
    public string ApplicationName => "E-Commerce API";
}

Registration:

builder.Services.AddSingleton<
    IApplicationConfiguration,
    ApplicationConfiguration>();

Now multiple requests can use the same instance.

Request 1 ----+
              |
Request 2 ----+----> ApplicationConfiguration
              |
Request 3 ----+

11. Real-Time Example – Product Cache

Suppose an application frequently reads product categories.

Instead of querying the database repeatedly, we may maintain an application-level cache.

Conceptually:

public class ProductCache
{
    private readonly Dictionary<int, string> _products
        = new();

    public void Add(int id, string name)
    {
        _products[id] = name;
    }

    public string? Get(int id)
    {
        return _products.TryGetValue(id, out var name)
            ? name
            : null;
    }
}

Registration:

builder.Services.AddSingleton<ProductCache>();

The same cache object can be shared by requests.

However, when using mutable shared state, thread safety must be considered carefully. A singleton should not simply contain an ordinary mutable collection and assume concurrent requests are safe.


12. Complete E-Commerce Example

Let's build a simplified dependency chain.

OrdersController
       |
       v
OrderService
       |
       +---- OrderRepository
       |
       +---- PaymentService
       |
       +---- EmailService
       |
       +---- ApplicationCache
       |
       v
ApplicationDbContext

Possible registrations:

builder.Services.AddControllers();

builder.Services.AddScoped<IOrderService, OrderService>();

builder.Services.AddScoped<IOrderRepository, OrderRepository>();

builder.Services.AddScoped<IPaymentService, PaymentService>();

builder.Services.AddTransient<IEmailService, EmailService>();

builder.Services.AddSingleton<ProductCache>();

builder.Services.AddDbContext<ApplicationDbContext>();

Now each service has an appropriate lifetime based on its responsibilities.


13. What Happens During an HTTP Request?

Let's follow a request step-by-step.

Request:

POST /api/orders

Step 1 – Request Arrives

The client sends:

POST /api/orders

ASP.NET Core receives the request.


Step 2 – Request Scope Is Created

ASP.NET Core creates a scope for the HTTP request.

Request Scope
     |
     +-------------------------+
     |                         |
     v                         v
Scoped Services          Other Dependencies

Step 3 – Controller Is Created

ASP.NET Core needs:

OrdersController

Its constructor requires:

IOrderService

The DI container resolves it.


Step 4 – Scoped OrderService Is Created

Because it is registered as:

AddScoped<IOrderService, OrderService>()

ASP.NET Core creates one instance for this request.


Step 5 – Dependencies Are Resolved

Suppose OrderService requires:

IOrderRepository
IPaymentService
IEmailService

The DI container resolves them.

OrderService
     |
     +-- OrderRepository    Scoped
     |
     +-- PaymentService     Scoped
     |
     +-- EmailService       Transient

Step 6 – DbContext Is Resolved

OrderRepository requires:

ApplicationDbContext

Because DbContext is scoped, the request receives its scoped instance.

OrderService
      |
      v
OrderRepository
      |
      v
ApplicationDbContext

Step 7 – Singleton Is Resolved

Suppose ProductCache is needed.

The DI container checks:

Is ProductCache already created?

If yes:

Use existing instance

If no:

Create singleton instance

Step 8 – Request Executes

The order is processed.

Validate Order
      |
      v
Check Product
      |
      v
Create Order
      |
      v
Process Payment
      |
      v
Save Database Changes
      |
      v
Send Notification

Step 9 – Request Completes

The HTTP request ends.

ASP.NET Core disposes the request scope.

Scoped and transient disposable services associated with that scope are disposed according to the DI container's lifetime management.

The singleton remains alive while its container remains alive.


14. Visualizing All Three Lifetimes

Imagine three HTTP requests.

                  APPLICATION
                      |
             Singleton Instance
                      |
          +-----------+-----------+
          |           |           |
       Request 1   Request 2   Request 3
          |           |           |
       Scoped A    Scoped B    Scoped C
          |           |           |
       Transient   Transient   Transient
          A           B           C

The important point is:

Singleton
   |
   +-- Same instance across requests

Scoped
   |
   +-- Same instance within one request
   +-- Different instance for another request

Transient
   |
   +-- New instance whenever resolved

15. Important Difference: Scoped vs Transient

This is a common interview question.

Suppose:

builder.Services.AddScoped<IService, MyService>();

and the same service is requested twice within one scope.

Conceptually:

Request
  |
  +-- Resolve IService ---> Instance A
  |
  +-- Resolve IService ---> Instance A

Same instance.

With:

builder.Services.AddTransient<IService, MyService>();

you can get:

Request
  |
  +-- Resolve IService ---> Instance A
  |
  +-- Resolve IService ---> Instance B

Different instances.


16. Important Difference: Scoped vs Singleton

Scoped:

Request 1 ---> Instance A
Request 2 ---> Instance B
Request 3 ---> Instance C

Singleton:

Request 1 ---+
Request 2 ---+---> Same Instance A
Request 3 ---+

Therefore, singleton services should be designed very carefully because multiple requests can access the same instance concurrently.


17. Can Singleton Depend on Scoped Service?

This is an important interview question.

Suppose:

builder.Services.AddSingleton<MySingleton>();
builder.Services.AddScoped<MyScoped>();

and:

public class MySingleton
{
    private readonly MyScoped _scoped;

    public MySingleton(MyScoped scoped)
    {
        _scoped = scoped;
    }
}

This creates a lifetime mismatch.

A singleton lives much longer than a scoped service.

Conceptually:

Singleton
     |
     v
Scoped Service

The scoped service cannot naturally live for the entire lifetime of the singleton.

ASP.NET Core's DI validation can detect such captive dependency problems in appropriate environments/configurations.

General rule:

Singleton
   ↓
Should not directly depend on
   ↓
Scoped

18. Can Scoped Depend on Singleton?

Yes.

For example:

Scoped OrderService
        |
        v
Singleton ApplicationConfiguration

This is generally valid because the singleton has a lifetime longer than the scoped service.


19. Can Transient Depend on Scoped?

Within a valid request scope, yes.

For example:

Scoped OrderService
        |
        v
Transient EmailFormatter

The transient object is created for the resolution and can use dependencies available in that scope.


20. Can Singleton Depend on Transient?

This requires careful consideration.

Technically, a singleton can resolve a transient dependency during its construction, but that transient instance then effectively becomes held by the singleton for as long as the singleton holds it.

Therefore, the dependency's effective lifetime can become much longer than intended.

This is sometimes called a captive dependency.

So don't choose lifetimes merely because the DI container allows the registration.

Choose them based on the object's state and responsibilities.


21. Service Lifetime and Thread Safety

This is especially important for Singleton services.

Imagine:

public class CounterService
{
    private int _count;

    public void Increment()
    {
        _count++;
    }
}

If registered as:

builder.Services.AddSingleton<CounterService>();

multiple requests may access the same object concurrently.

Therefore:

Request A ----+
              |
Request B ----+----> Same Singleton
              |
Request C ----+

The implementation must be safe for concurrent access.

For shared mutable state, use appropriate thread-safe techniques or concurrency-safe collections where necessary.


22. Service Lifetime in Microservices

In a microservices architecture, each microservice normally has its own DI container and process/application lifetime.

For example:

Order Service
     |
     +-- Singleton
     +-- Scoped
     +-- Transient

Payment Service
     |
     +-- Singleton
     +-- Scoped
     +-- Transient

Inventory Service
     |
     +-- Singleton
     +-- Scoped
     +-- Transient

A singleton in the Order Service is not automatically shared with the Payment Service.

Each application has its own process/container and its own DI registrations.


23. Service Lifetime vs Database Lifetime

Don't confuse these concepts.

Service lifetime:

Transient
Scoped
Singleton

Database connection lifetime is a separate concern.

For example:

Web API
   |
   v
DbContext
   |
   v
Database Provider
   |
   v
Database

DbContext is normally scoped, while the underlying database connection management is handled by the database provider and connection pooling mechanisms.


24. Common Real-Time Registration

A typical ASP.NET Core application might contain:

builder.Services.AddControllers();

builder.Services.AddDbContext<ApplicationDbContext>();

builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

builder.Services.AddScoped<IPaymentService, PaymentService>();

builder.Services.AddTransient<IEmailTemplateService, EmailTemplateService>();

builder.Services.AddSingleton<IApplicationSettings, ApplicationSettings>();

A reasonable conceptual mapping is:

ServiceTypical LifetimeReason
DbContextScopedRequest/unit-of-work oriented
RepositoryScopedWorks with DbContext
Business ServiceScopedRequest-level operation
Stateless FormatterTransientLightweight/stateless
Application ConfigurationSingletonShared application-level data
In-memory CacheSingletonShared cache, if designed safely

These are common patterns, not absolute rules.


25. Service Life Cycle and IDisposable

Another important concept is disposal.

Suppose:

public class FileService : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }
}

If the DI container creates and owns a disposable service, it generally manages its disposal according to the service lifetime and scope.

For example, a scoped disposable service is normally disposed when its request scope ends.

A singleton disposable service is normally disposed when the application's DI container is disposed.

This is one reason you should generally let the DI container manage dependencies that it creates rather than manually disposing injected dependencies.


26. Service Lifetime and Background Services

A common mistake is trying to inject a scoped service directly into a long-running BackgroundService.

For example:

public class OrderBackgroundService : BackgroundService
{
    private readonly ApplicationDbContext _context;

    public OrderBackgroundService(ApplicationDbContext context)
    {
        _context = context;
    }
}

This is problematic because BackgroundService is effectively long-lived, while DbContext is scoped.

A better approach is to create a scope when processing each unit of work:

public class OrderBackgroundService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public OrderBackgroundService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        using var scope = _scopeFactory.CreateScope();

        var dbContext =
            scope.ServiceProvider
                 .GetRequiredService<ApplicationDbContext>();

        // Process work
    }
}

For repeated background work, create and dispose an appropriate scope for each unit of work rather than keeping one scoped dependency forever.


27. How DI Container Resolves a Service

Suppose:

builder.Services.AddScoped<IOrderService, OrderService>();

and:

public class OrderService : IOrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository)
    {
        _repository = repository;
    }
}

When the controller requests IOrderService, the DI container performs roughly this process:

1. Controller requests IOrderService
             |
             v
2. DI Container checks registration
             |
             v
3. Finds OrderService
             |
             v
4. Checks OrderService lifetime
             |
             v
5. Scoped
             |
             v
6. Checks current scope
             |
             v
7. Creates OrderService if not already created
             |
             v
8. Sees IOrderRepository dependency
             |
             v
9. Resolves IOrderRepository
             |
             v
10. Creates OrderService
             |
             v
11. Injects dependencies
             |
             v
12. Controller receives OrderService

This is the essence of Dependency Injection.


28. Real-Time Request Example

Let's assume:

Customer places an order

Request:

POST /api/orders

Flow:

Client
  |
  | HTTP Request
  v
ASP.NET Core
  |
  v
Middleware Pipeline
  |
  v
Request Scope
  |
  v
OrdersController
  |
  v
OrderService
  |
  +---- OrderRepository
  |          |
  |          v
  |      DbContext
  |
  +---- PaymentService
  |
  +---- EmailService
  |
  +---- ProductCache
  |
  v
Database

Lifetimes might be:

OrderService        -> Scoped
OrderRepository     -> Scoped
DbContext           -> Scoped
PaymentService      -> Scoped
EmailService        -> Transient
ProductCache        -> Singleton

At the end:

HTTP Request Ends
       |
       v
Request Scope Disposed
       |
       +-- Scoped services disposed
       +-- Request-owned transient disposables disposed
       |
       v
Singleton remains available

29. Common Interview Questions

Q1. What are the three service lifetimes?

Answer:

Transient
Scoped
Singleton

Q2. What is Transient?

A new service instance is created each time the service is requested from the DI container.

services.AddTransient<IEmailService, EmailService>();

Q3. What is Scoped?

One service instance is generally created per scope. In ASP.NET Core Web API, this normally means one instance per HTTP request.

services.AddScoped<IOrderService, OrderService>();

Q4. What is Singleton?

One service instance is reused for the lifetime of the DI container/application.

services.AddSingleton<ICache, Cache>();

Q5. Which lifetime is normally used for DbContext?

DbContext is normally registered as Scoped in ASP.NET Core applications.


Q6. Why shouldn't DbContext normally be Singleton?

Because DbContext is designed around a unit-of-work pattern and is not intended to be shared concurrently across unrelated requests.


Q7. Which lifetime is best for stateless lightweight services?

Often Transient, although Scoped can also be appropriate depending on the service's dependencies and design.


Q8. Which lifetime is best for shared application-wide state?

A Singleton can be appropriate, but shared mutable state must be designed for concurrent access and application lifetime.


Q9. Can Singleton depend on Scoped?

Generally, no direct dependency should be created, because it creates a lifetime mismatch/captive dependency.


Q10. Can Scoped depend on Singleton?

Yes. This is generally valid.


30. Common Mistakes

Mistake 1 – Making Everything Singleton

Avoid:

services.AddSingleton<OrderService>();
services.AddSingleton<OrderRepository>();
services.AddSingleton<ApplicationDbContext>();

This can create serious lifetime and concurrency problems.


Mistake 2 – Making Everything Transient

Using transient everywhere can cause unnecessary object creation and can undermine intentional request-level sharing.


Mistake 3 – Ignoring Thread Safety

Singleton services may be accessed concurrently.

Never assume:

Singleton = Automatically Thread Safe

It is not.


Mistake 4 – Injecting Scoped Services into Long-Lived Services

For example:

BackgroundService
       |
       v
DbContext

Instead, create a scope when processing the background operation.


31. Easy Way to Remember

Remember this formula:

Transient = Every Time

Scoped = Every Request

Singleton = Entire Application

Or:

Transient
    ↓
New Object

Scoped
    ↓
One Object Per Scope

Singleton
    ↓
One Object Per Container Lifetime

32. Final Comparison

FeatureTransientScopedSingleton
New instance frequently?YesNoNo
Same instance within request?Not necessarilyYesYes
Same instance across requests?NoNoYes
Typical lifetimeResolutionScope/requestApplication/container
Thread safety concernUsually less shared stateUsually less shared across requestsHigh if mutable
DbContext
RepositoryUsually Scoped
Stateless lightweight serviceSometimesSometimes
Application-wide cache✅, if designed safely
Configuration serviceSometimesSometimesOften

33. Complete Mental Model

When you see:

builder.Services.AddTransient<A>();
builder.Services.AddScoped<B>();
builder.Services.AddSingleton<C>();

think:

                    DI CONTAINER
                         |
        +----------------+----------------+
        |                |                |
        v                v                v
    TRANSIENT         SCOPED          SINGLETON
        |                |                |
     New Object       One per Scope    One per Container
        |                |                |
        |           HTTP Request       Application
        |                |                |
        v                v                v
     A1, A2...          B1              C1
                       B2              C1
                       B3              C1

The key idea is that service lifetime is not merely about object creation—it determines how long the object can retain state and who can share that state.


Conclusion

Service Lifetime is a fundamental part of Dependency Injection in ASP.NET Core.

The three primary lifetimes are:

Transient → New instance when resolved

Scoped → One instance per scope/request

Singleton → One instance for the DI container lifetime

For a real-world e-commerce Web API, a common design is:

Controller
    |
    v
OrderService        → Scoped
    |
    +---- Repository → Scoped
    |
    +---- DbContext  → Scoped
    |
    +---- Payment    → Scoped
    |
    +---- Email      → Transient
    |
    +---- Cache      → Singleton

Choosing the correct lifetime is important for:

  • Performance

  • Memory management

  • Thread safety

  • Database consistency

  • Resource management

  • Scalability

  • Application stability

The most important interview rule to remember is:

Transient = new instance, Scoped = one instance per scope/request, Singleton = one instance for the container lifetime.

Once you understand this concept, Dependency Injection, DbContext, middleware, background services, repositories, caching, and ASP.NET Core application architecture become much easier to understand.

Don't Copy

Protected by Copyscape Online Plagiarism Checker