Monday, August 31, 2026

Azure Red Hat OpenShift (ARO) with .NET Core — Complete Guide with Real-Time Example

 

Important clarification: Azure Red Hat OpenShift (ARO) is not mandatory for .NET Core/.NET applications. It is an enterprise container platform that can be an excellent choice when an organization wants OpenShift/Kubernetes capabilities with Azure-managed infrastructure. Microsoft describes ARO as a fully managed OpenShift service jointly operated and supported by Microsoft and Red Hat. (Microsoft Learn)


1. What is Azure Red Hat OpenShift?

Azure Red Hat OpenShift (ARO) is Microsoft's managed Red Hat OpenShift service running on Azure.

In simple terms:

.NET Application
       ↓
Docker Container
       ↓
OpenShift
       ↓
Azure Red Hat OpenShift
       ↓
Microsoft Azure

OpenShift extends Kubernetes with additional enterprise capabilities and developer/operator tooling. ARO gives you those OpenShift capabilities without requiring your team to manually operate the underlying OpenShift control plane and infrastructure. Microsoft and Red Hat jointly engineer, operate, and support the service. (Microsoft Learn)

ARO clusters are deployed into your Azure subscription, while Microsoft and Red Hat handle major platform-management responsibilities such as patching and monitoring of the managed cluster components. (Microsoft Learn)


2. Why do we need OpenShift?

Imagine an organization has 50 microservices:

                    API Gateway
                        |
        +---------------+---------------+
        |               |               |
   Order Service   Payment Service   Customer Service
        |               |               |
      SQL DB          SQL DB          Redis
        |
   Notification
      Service

Each service may have:

  • Different deployments

  • Different versions

  • Different resource requirements

  • Multiple replicas

  • Health checks

  • Networking requirements

  • Secrets

  • Configuration

  • Logging

  • Monitoring

  • Autoscaling

Managing all of this manually becomes difficult.

A container orchestration platform can manage these workloads.

OpenShift provides Kubernetes-based orchestration plus additional platform capabilities. ARO packages this as a managed Azure service. (Microsoft Learn)


3. Is Azure OpenShift mandatory for .NET Core?

❌ No.

This is one of the most important points.

A .NET application can run on:

IIS
Azure App Service
Azure Container Apps
Azure Kubernetes Service
Azure Red Hat OpenShift
Windows Services
Linux
Docker
Virtual Machines
On-premises servers

For example:

ASP.NET Core Web API
       |
       +----> IIS
       |
       +----> Azure App Service
       |
       +----> Docker
       |
       +----> AKS
       |
       +----> ARO

So .NET does not require OpenShift.

The decision depends on your organization's architecture, operational requirements, existing platform standards, compliance requirements, and team expertise.


4. Why would an enterprise choose ARO?

ARO becomes particularly interesting when an organization wants:

1. Kubernetes

OpenShift is built on Kubernetes.

2. Enterprise OpenShift platform

Organizations that already standardize on Red Hat OpenShift can use ARO while keeping their workloads on Azure.

3. Managed platform

Microsoft and Red Hat manage important parts of the platform, reducing the amount of cluster administration required from application teams. (Microsoft Learn)

4. Containerized microservices

.NET applications can be packaged as containers and deployed as independent workloads.

5. Scaling

Multiple replicas of a service can run simultaneously.

6. Security and identity

ARO supports integration with Microsoft Entra ID and Kubernetes RBAC. (Microsoft Learn)

7. CI/CD

It can be integrated with enterprise CI/CD pipelines.

8. Hybrid-cloud strategy

OpenShift can be useful for organizations that want consistency between OpenShift environments across different infrastructure.


5. ARO Architecture

A simplified architecture looks like this:

                   Internet
                      |
                      ↓
                Azure Front Door
                      |
                      ↓
             Application Gateway
                      |
                      ↓
             Azure Red Hat OpenShift
              ┌───────────────────┐
              │    OpenShift      │
              │     Cluster       │
              │                   │
              │  ┌─────────────┐  │
              │  │   Router    │  │
              │  └──────┬──────┘  │
              │         │         │
              │  ┌──────┴──────┐  │
              │  │   Service   │  │
              │  └──────┬──────┘  │
              │         │         │
              │   ┌─────┴─────┐   │
              │   │   Pods    │   │
              │   │           │   │
              │   │ .NET API  │   │
              │   │ .NET API  │   │
              │   │ .NET API  │   │
              │   └───────────┘   │
              └───────────────────┘
                       |
                Azure SQL Database

ARO provides single-tenant, high-availability OpenShift clusters on Azure. (Microsoft Learn)


6. Important OpenShift Terminology

Before deploying .NET, understand these concepts.

Cluster

The complete OpenShift environment.

ARO Cluster
   |
   +-- Control Plane
   |
   +-- Worker Nodes
   |
   +-- Networking
   |
   +-- Storage
   |
   +-- Operators

Node

A node is a machine that runs workloads.

Worker Node
   |
   +-- Pod
   +-- Pod
   +-- Pod

Pod

A Pod is the basic Kubernetes/OpenShift unit in which containers run.

For a simple .NET API:

Pod
 |
 +-- .NET Container

If you have three replicas:

Pod 1 → .NET API
Pod 2 → .NET API
Pod 3 → .NET API

Deployment

Defines how your application should be deployed.

For example:

replicas: 3

means we want three instances.


Service

Provides stable networking to the Pods.

             Service
                |
       +--------+--------+
       |        |        |
      Pod      Pod      Pod

Route

OpenShift Route exposes an application outside the cluster.

Internet
   |
   ↓
OpenShift Route
   |
   ↓
Service
   |
   ↓
Pods

7. Real-Time .NET Microservices Example

Let's design an e-commerce application.

We have:

E-Commerce
    |
    +-- Product Service
    |
    +-- Order Service
    |
    +-- Payment Service
    |
    +-- Inventory Service
    |
    +-- Notification Service

Suppose the Order Service is built using:

ASP.NET Core Web API
.NET 10
C#
Entity Framework Core
SQL Server
Docker

We want:

                 Client
                   |
                   ↓
              OpenShift
                   |
             Order Service
                   |
        +----------+----------+
        |          |          |
        ↓          ↓          ↓
    Payment    Inventory   Notification

8. Create the .NET API

Create the project:

dotnet new webapi -n OrderService
cd OrderService

Example API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

Controller:

using Microsoft.AspNetCore.Mvc;

namespace OrderService.Controllers;

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetOrder(int id)
    {
        return Ok(new
        {
            OrderId = id,
            Product = "Laptop",
            Quantity = 2,
            Status = "Confirmed"
        });
    }
}

Now:

GET /api/orders/1001

returns:

{
  "orderId": 1001,
  "product": "Laptop",
  "quantity": 2,
  "status": "Confirmed"
}

9. Containerize the .NET Application

OpenShift runs containerized workloads, so we can create a Docker image for our API.

Example Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

WORKDIR /src

COPY . .

RUN dotnet restore

RUN dotnet publish -c Release \
    -o /app/publish \
    --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:10.0

WORKDIR /app

COPY --from=build /app/publish .

EXPOSE 8080

ENTRYPOINT ["dotnet", "OrderService.dll"]

The basic flow becomes:

C# Code
   ↓
dotnet publish
   ↓
Docker Image
   ↓
Container Registry
   ↓
OpenShift
   ↓
Pod

10. Build the Docker Image

docker build -t orderservice:1.0 .

Test locally:

docker run -p 8080:8080 orderservice:1.0

Then:

http://localhost:8080/api/orders/1001

11. Push Image to Container Registry

In an enterprise environment, you could use a container registry such as:

Azure Container Registry

Conceptually:

docker tag orderservice:1.0 \
    myregistry.azurecr.io/orderservice:1.0

docker push \
    myregistry.azurecr.io/orderservice:1.0

Now the image is available to OpenShift.


12. Deploy the .NET Application to OpenShift

A Kubernetes/OpenShift Deployment could look like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3

  selector:
    matchLabels:
      app: order-service

  template:
    metadata:
      labels:
        app: order-service

    spec:
      containers:
        - name: order-service

          image: myregistry.azurecr.io/orderservice:1.0

          ports:
            - containerPort: 8080

          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"

            limits:
              cpu: "500m"
              memory: "512Mi"

Notice:

replicas: 3

OpenShift will run three Pods.

             Order Service
                   |
       +-----------+-----------+
       |           |           |
       ↓           ↓           ↓
     Pod 1       Pod 2       Pod 3

If one Pod becomes unavailable, the platform can maintain the desired replica state.


13. Create an OpenShift Service

Create:

apiVersion: v1
kind: Service

metadata:
  name: order-service

spec:
  selector:
    app: order-service

  ports:
    - port: 80
      targetPort: 8080

Now:

Service
   |
   +---- Pod 1
   |
   +---- Pod 2
   |
   +---- Pod 3

The application doesn't need to know which Pod receives the request.


14. Expose the API using an OpenShift Route

Example:

apiVersion: route.openshift.io/v1
kind: Route

metadata:
  name: order-service

spec:
  to:
    kind: Service
    name: order-service

  port:
    targetPort: 8080

The resulting flow is:

Client
  |
  | HTTPS
  ↓
OpenShift Route
  |
  ↓
Service
  |
  +--------+--------+
  |        |        |
 Pod 1    Pod 2    Pod 3

15. Deploy Using oc

OpenShift provides the oc command-line client.

For example:

oc login <your-cluster>

Create/select a project:

oc new-project ecommerce

Deploy:

oc apply -f deployment.yaml

Service:

oc apply -f service.yaml

Route:

oc apply -f route.yaml

Check Pods:

oc get pods

Example:

NAME                             READY   STATUS
order-service-7c8d9f7c5d-abc12   1/1     Running
order-service-7c8d9f7c5d-def34   1/1     Running
order-service-7c8d9f7c5d-ghi56   1/1     Running

16. What Happens When Traffic Increases?

Suppose:

Normal traffic
   ↓
3 Pods

During a festival sale:

Traffic increases
       ↓
Autoscaling
       ↓
5 Pods
       ↓
10 Pods

Conceptually:

              Service
                 |
       +---------+---------+
       |         |         |
      Pod       Pod       Pod
       |         |         |
       +---------+---------+
                 |
            More traffic
                 ↓
          Additional Pods

This is one of the major advantages of container orchestration.


17. Health Checks

A production .NET application shouldn't simply be considered healthy because its process is running.

ASP.NET Core provides health-check support.

Example:

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/health");

app.MapControllers();

app.Run();

Now:

GET /health

can be used by the platform to determine application health.


18. Configure Kubernetes/OpenShift Probes

For example:

livenessProbe:
  httpGet:
    path: /health
    port: 8080

  initialDelaySeconds: 10
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /health
    port: 8080

  initialDelaySeconds: 5
  periodSeconds: 5

There are two important concepts:

Liveness

"Is my application alive?"

Readiness

"Can my application receive traffic?"

This distinction is extremely important in microservices.


19. Configuration Management

Don't hard-code:

var connectionString =
    "Server=production-server;Database=Orders...";

Instead use configuration:

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

Environment-specific values can then be supplied through OpenShift configuration mechanisms.

For example:

env:
  - name: ASPNETCORE_ENVIRONMENT
    value: Production

  - name: ConnectionStrings__OrdersDb
    valueFrom:
      secretKeyRef:
        name: orders-db-secret
        key: connection-string

20. Secrets

Never put passwords directly into:

Dockerfile
Git repository
deployment.yaml
source code

Instead use a Secret.

Example:

oc create secret generic orders-db-secret \
  --from-literal=connection-string="YOUR-CONNECTION-STRING"

Then the application can consume it through an environment variable.

For enterprise Azure environments, you can also integrate workloads with Azure identity capabilities rather than relying on long-lived credentials. ARO supports managed and workload identities. (Microsoft Learn)


21. Scaling a .NET Microservice

Suppose:

Order Service
Current replicas = 3

You can scale manually:

oc scale deployment/order-service --replicas=5

Now:

Order Service
     |
     +-- Pod 1
     +-- Pod 2
     +-- Pod 3
     +-- Pod 4
     +-- Pod 5

In production, autoscaling can be configured based on resource utilization and other supported metrics.


22. Rolling Deployment

Suppose version 1.0 is running:

Pod 1 → v1
Pod 2 → v1
Pod 3 → v1

You deploy:

v2

The platform can perform a rolling update rather than stopping every instance simultaneously.

Conceptually:

Pod 1 → v2
Pod 2 → v1
Pod 3 → v1

Pod 1 → v2
Pod 2 → v2
Pod 3 → v1

Pod 1 → v2
Pod 2 → v2
Pod 3 → v2

This helps reduce application downtime during deployments.


23. Real-Time E-Commerce Architecture

Now let's put everything together.

                         Customers
                             |
                             ↓
                    Azure Front Door
                             |
                             ↓
                    OpenShift Route
                             |
                             ↓
                      API Gateway
                             |
       +---------------------+---------------------+
       |                     |                     |
       ↓                     ↓                     ↓
 Order Service        Product Service       Customer Service
       |                     |                     |
       ↓                     ↓                     ↓
 Payment Service       Inventory Service      Redis
       |
       ↓
 Notification Service
       |
       ↓
 Azure Service Bus

Each microservice can have:

.NET API
   ↓
Docker Image
   ↓
Container Registry
   ↓
OpenShift Deployment
   ↓
Pods
   ↓
Service
   ↓
Route

24. How .NET and OpenShift Work Together

The relationship is important to understand:

.NET
 ↓
Application Framework

Docker
 ↓
Application Packaging

Kubernetes
 ↓
Container Orchestration

OpenShift
 ↓
Enterprise Kubernetes Platform

Azure Red Hat OpenShift
 ↓
Managed OpenShift on Azure

So ARO isn't replacing .NET.

It provides the platform on which your containerized .NET applications can run.


25. OpenShift vs AKS

This is a very common interview question.

FeatureAKSAzure Red Hat OpenShift
TechnologyKubernetesOpenShift/Kubernetes
Azure managed serviceYesYes
MicrosoftManaged serviceJointly operated with Red Hat
Red Hat ecosystemNot centralStrong
OpenShift toolingNoYes
Kubernetes workloadsYesYes
.NET supportYesYes
Enterprise OpenShift standardNoYes
Best fitAzure/Kubernetes environmentsOrganizations standardized on OpenShift

Microsoft explicitly describes ARO as an Azure-managed OpenShift service jointly engineered, operated, and supported by Microsoft and Red Hat. (Microsoft Learn)


26. OpenShift vs Azure App Service

These solve different problems.

Azure App Service

Best when:

I have a web application/API
        ↓
I want managed hosting
        ↓
I don't need Kubernetes

ARO

Better suited when:

I have many containerized services
        ↓
I need Kubernetes/OpenShift
        ↓
I need enterprise container orchestration

Don't introduce OpenShift simply because you are using .NET.


27. OpenShift vs Docker

Another common misunderstanding:

Docker and OpenShift are not competitors in the same sense.

Docker packages the application.

OpenShift orchestrates containerized workloads.

.NET Application
       ↓
Docker Image
       ↓
OpenShift
       ↓
Pods
       ↓
Services
       ↓
Routes

28. Why .NET Applications Work Well on OpenShift

Red Hat provides supported .NET container images and OpenShift guidance for .NET applications. For example, Red Hat's documentation describes dotnet SDK images and corresponding runtime/ASP.NET runtime image streams for OpenShift. (Red Hat Documentation)

That means an enterprise can standardize on:

.NET
+
Red Hat Enterprise Linux/UBI-based containers
+
OpenShift
+
Azure

For example:

.NET 10
   ↓
ASP.NET Core
   ↓
Red Hat UBI-based container
   ↓
OpenShift
   ↓
Azure Red Hat OpenShift

Red Hat's current documentation includes guidance for running .NET 10 on OpenShift and provides dotnet, dotnet-runtime, and dotnet-aspnet image streams. (Red Hat Documentation)


29. CI/CD Pipeline

A typical enterprise pipeline might look like:

Developer
    |
    ↓
Git Repository
    |
    ↓
CI Pipeline
    |
    +-- Build
    +-- Unit Tests
    +-- SonarQube
    +-- Security Scan
    +-- Docker Build
    |
    ↓
Container Registry
    |
    ↓
CD Pipeline
    |
    ↓
ARO
    |
    ↓
OpenShift Deployment
    |
    ↓
Pods

For example:

docker build -t orderservice:1.0 .
docker push myregistry.azurecr.io/orderservice:1.0

oc apply -f deployment.yaml

In a real enterprise pipeline, image tags would normally be immutable version/build identifiers rather than simply latest.


30. What Happens When a Pod Fails?

Suppose:

Order Service

Pod 1 → Running
Pod 2 → Running
Pod 3 → Failed

The desired state says:

replicas = 3

The orchestration layer can create another Pod:

Pod 1 → Running
Pod 2 → Running
Pod 3 → Failed
Pod 4 → Starting

Eventually:

Pod 1 → Running
Pod 2 → Running
Pod 4 → Running

The application therefore doesn't have to manually detect and recreate failed instances.


31. Production Architecture

A mature enterprise architecture could look like:

                    Internet
                       |
                       ↓
                Azure Front Door
                       |
                       ↓
              WAF / Load Balancing
                       |
                       ↓
              Azure Red Hat OpenShift
                       |
        +--------------+--------------+
        |              |              |
        ↓              ↓              ↓
   API Gateway      Services       Workers
        |              |              |
        |        +-----+-----+        |
        |        |     |     |        |
        ↓        ↓     ↓     ↓        ↓
     Orders   Payment Inventory Customer
        |
        ↓
  Azure Service Bus
        |
        ↓
 Notification Worker
        |
        ↓
 External Systems

Supporting services:

Azure SQL
Azure Cache for Redis
Azure Key Vault
Azure Container Registry
Application monitoring
Centralized logging
CI/CD

32. Is ARO Mandatory in Microservices?

Again:

❌ No.

Microservices can run on:

AKS
ARO
Azure Container Apps
Docker Compose
ECS
GKE
Self-managed Kubernetes
Virtual Machines
Other container platforms

The correct architecture decision is:

Business Requirements
        ↓
Non-functional Requirements
        ↓
Platform Requirements
        ↓
Choose Hosting Platform

Not:

.NET
 ↓
Must use ARO ❌

33. When Should You Choose ARO?

Choose ARO when your organization needs things such as:

Enterprise OpenShift standardization

Your company already uses OpenShift across environments.

Kubernetes + OpenShift ecosystem

You need OpenShift-specific platform capabilities and workflows.

Managed Azure deployment

You want OpenShift deployed into Azure while Microsoft and Red Hat handle important platform operations. (Microsoft Learn)

Large microservices platform

You have many containerized services and need centralized orchestration.

Hybrid-cloud consistency

You want an OpenShift-based application platform that can fit into broader hybrid-cloud strategies.

Enterprise security/governance

You need platform-level identity, RBAC, networking, policy, and operational controls.


34. When Should You NOT Choose ARO?

If you have:

One ASP.NET Core API

and your requirement is simply:

Host the API

ARO could be unnecessary complexity.

You might choose:

Azure App Service

instead.

Likewise, if your team already has strong Azure Kubernetes expertise and doesn't need OpenShift, AKS may be a more natural choice.


35. ARO Interview Question

Q: Is Azure Red Hat OpenShift mandatory for .NET Core?

Answer:

No. Azure Red Hat OpenShift is not mandatory for .NET Core applications. .NET applications can run on Azure App Service, Azure Container Apps, AKS, virtual machines, Docker, and other platforms. ARO is a managed OpenShift platform on Azure and is mainly selected when an organization needs enterprise OpenShift/Kubernetes capabilities, OpenShift standardization, hybrid-cloud consistency, or specific operational and governance requirements.

That's the answer I'd recommend giving in a .NET Architect interview.


36. The Most Important Concept

Remember this hierarchy:

             .NET
              │
              │ builds
              ↓
        ASP.NET Core API
              │
              │ packaged as
              ↓
          Docker Image
              │
              │ deployed to
              ↓
          OpenShift
              │
              │ hosted on
              ↓
       Azure Red Hat OpenShift
              │
              ↓
           Azure

ARO is the hosting/orchestration platform—not a requirement of the .NET framework.


37. Final Summary

Azure Red Hat OpenShift combines Azure + Red Hat OpenShift + Kubernetes-based container orchestration + managed platform operations. Microsoft says ARO provides fully managed OpenShift clusters, with Microsoft and Red Hat jointly engineering, operating, and supporting the service. (Microsoft Learn)

For a .NET microservices application:

.NET Microservice
       ↓
Docker Container
       ↓
Container Registry
       ↓
Azure Red Hat OpenShift
       ↓
OpenShift Deployment
       ↓
Pods
       ↓
Service
       ↓
Route
       ↓
Users

The key takeaway is:

Don't choose ARO because you use .NET. Choose ARO when your application's operational, architectural, enterprise, or organizational requirements justify an OpenShift platform.

Official references

Azure Red Hat OpenShift — Microsoft Learn

Azure Red Hat OpenShift documentation

.NET on OpenShift — Red Hat documentation

Create an Azure Red Hat OpenShift cluster

If you're publishing this as a blog, a strong title would be “Azure Red Hat OpenShift (ARO) with .NET 10: Complete Guide to Deploying ASP.NET Core Microservices with Real-Time E-Commerce Example”.

Worker Services in .NET — Complete Guide


1. What is a Worker Service?

A Worker Service is a .NET application designed to run continuously in the background.

Unlike an ASP.NET Core Web API:

Web API
   ↓
HTTP Request
   ↓
Controller
   ↓
Business Logic
   ↓
HTTP Response

A Worker Service generally works like:

Worker Service
      ↓
Background Process
      ↓
Read Message / Timer / Event
      ↓
Business Logic
      ↓
Database / External API
      ↓
Continue Processing

A Worker Service can run as:

  • Windows Service

  • Linux systemd service

  • Docker container

  • Kubernetes Pod

  • Azure Container Apps

  • Azure App Service background process in appropriate hosting models

  • VM-hosted process

  • Kubernetes CronJob for scheduled/batch work


2. Why do we need Worker Services?

Consider an e-commerce application.

A customer places an order:

Customer
   ↓
Order API
   ↓
Create Order
   ↓
Return response

But after creating the order, many things may need to happen:

Order Created
    │
    ├── Send Email
    ├── Generate Invoice
    ├── Update Inventory
    ├── Send Notification
    ├── Create Shipment
    └── Update Analytics

Doing all of these inside the API request can make the API slow.

Instead:

                 ┌───────────────┐
Customer ───────►│   Order API   │
                 └───────┬───────┘
                         │
                         │ OrderCreated
                         ▼
                 ┌───────────────┐
                 │ Message Queue │
                 └───────┬───────┘
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
   Email Worker    Inventory Worker  Invoice Worker

This is where Worker Services become extremely useful.


3. Worker Service vs BackgroundService

These two terms are related but not exactly the same.

Worker Service

A Worker Service is a .NET project/application template designed for long-running background workloads.

You can create one using:

dotnet new worker -n OrderProcessing.Worker

It normally contains:

OrderProcessing.Worker
│
├── Program.cs
├── Worker.cs
└── appsettings.json

BackgroundService

BackgroundService is a base class provided by .NET for implementing long-running background work.

For example:

public class Worker : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine("Worker is running...");

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

So:

Worker Service
      │
      └── Hosting Model
             │
             └── BackgroundService
                    │
                    └── ExecuteAsync()

4. Basic Worker Service Architecture

A typical Worker Service looks like this:

┌─────────────────────────────────────┐
│         .NET Worker Service         │
│                                     │
│  Generic Host                       │
│       │                             │
│       ├── Dependency Injection      │
│       ├── Configuration             │
│       ├── Logging                   │
│       ├── Configuration             │
│       └── Hosted Services           │
│                  │                  │
│                  ▼                  │
│          BackgroundService          │
│                  │                  │
│                  ▼                  │
│           ExecuteAsync()            │
│                  │                  │
│                  ▼                  │
│          Business Processing        │
└─────────────────────────────────────┘

5. Creating a Worker Service

Create the project:

dotnet new worker -n OrderProcessing.Worker

Move into the project:

cd OrderProcessing.Worker

Run it:

dotnet run

The default template will create something similar to:

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

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

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation(
                "Worker running at: {time}",
                DateTimeOffset.Now);

            await Task.Delay(
                1000,
                stoppingToken);
        }
    }
}

6. Understanding ExecuteAsync()

This is the most important method.

protected override async Task ExecuteAsync(
    CancellationToken stoppingToken)

The Worker Service calls this method when the application starts.

For example:

Application Starts
       ↓
Host Starts
       ↓
Worker Starts
       ↓
ExecuteAsync()
       ↓
while loop
       ↓
Background processing

7. Why CancellationToken is important

Suppose your worker is running:

Worker
  ↓
Processing
  ↓
Processing
  ↓
Processing

Now Kubernetes sends a termination signal.

The worker should stop gracefully.

That's why we use:

CancellationToken stoppingToken

Example:

while (!stoppingToken.IsCancellationRequested)
{
    await ProcessOrderAsync(stoppingToken);
}

When cancellation occurs:

Kubernetes
     ↓
SIGTERM
     ↓
CancellationToken
     ↓
Worker stops

This is especially important in production microservices.


8. Real-Time Example — Order Processing Microservice

Let's build a realistic architecture.

Imagine an e-commerce system:

Customer
   ↓
Order API
   ↓
Azure Service Bus
   ↓
Order Processing Worker
   ↓
Order Database

The API doesn't need to process everything synchronously.


9. Step 1 — Order API

The API receives:

POST /api/orders

Request:

{
  "customerId": 1001,
  "productId": 5001,
  "quantity": 2
}

The API creates the order.

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> CreateOrder(
        CreateOrderRequest request)
    {
        var order = new Order
        {
            Id = Guid.NewGuid(),
            CustomerId = request.CustomerId,
            ProductId = request.ProductId,
            Quantity = request.Quantity,
            Status = "Pending"
        };

        // Save order

        // Publish OrderCreated event

        return Accepted(order);
    }
}

Notice:

return Accepted(order);

The API doesn't need to wait for the complete background processing.


10. Step 2 — Create an Event

Create:

public class OrderCreatedEvent
{
    public Guid OrderId { get; set; }

    public int CustomerId { get; set; }

    public int ProductId { get; set; }

    public int Quantity { get; set; }
}

The API publishes:

OrderCreatedEvent

to a message broker.

For example:

Azure Service Bus

Architecture:

Order API
    │
    │ OrderCreatedEvent
    ▼
Azure Service Bus
    │
    ▼
Order Processing Worker

11. Step 3 — Create Worker Service

Create:

dotnet new worker -n OrderProcessing.Worker

Install the Azure Service Bus package:

dotnet add package Azure.Messaging.ServiceBus

12. Configure Service Bus

appsettings.json:

{
  "ServiceBus": {
    "ConnectionString": "YOUR_CONNECTION_STRING",
    "QueueName": "orders"
  }
}

In production, don't put secrets directly into appsettings.json.

Use:

  • Azure Key Vault

  • Managed Identity

  • Environment variables

  • Kubernetes Secrets


13. Create Worker

public class OrderWorker : BackgroundService
{
    private readonly ILogger<OrderWorker> _logger;
    private readonly ServiceBusProcessor _processor;

    public OrderWorker(
        IConfiguration configuration,
        ILogger<OrderWorker> logger)
    {
        _logger = logger;

        var connectionString =
            configuration["ServiceBus:ConnectionString"];

        var queueName =
            configuration["ServiceBus:QueueName"];

        var client = new ServiceBusClient(connectionString);

        _processor = client.CreateProcessor(queueName);
    }

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        _processor.ProcessMessageAsync += ProcessMessage;
        _processor.ProcessErrorAsync += ProcessError;

        await _processor.StartProcessingAsync(
            stoppingToken);

        try
        {
            await Task.Delay(
                Timeout.Infinite,
                stoppingToken);
        }
        catch (OperationCanceledException)
        {
            // Application shutting down
        }

        await _processor.StopProcessingAsync();
    }

    private async Task ProcessMessage(
        ProcessMessageEventArgs args)
    {
        var messageBody =
            args.Message.Body.ToString();

        _logger.LogInformation(
            "Received Order: {Message}",
            messageBody);

        // Process order

        await args.CompleteMessageAsync(args.Message);
    }

    private Task ProcessError(
        ProcessErrorEventArgs args)
    {
        _logger.LogError(
            args.Exception,
            "Error processing message");

        return Task.CompletedTask;
    }
}

14. Register Worker in Program.cs

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<OrderWorker>();

var host = builder.Build();

host.Run();

This is the key line:

builder.Services.AddHostedService<OrderWorker>();

It tells .NET:

Start this Worker when the application starts.


15. Complete Processing Flow

Now the entire flow becomes:

             CUSTOMER
                 │
                 ▼
          ┌──────────────┐
          │   Order API  │
          └──────┬───────┘
                 │
                 │ Save Order
                 ▼
          ┌──────────────┐
          │ Azure SQL DB │
          └──────────────┘
                 │
                 │ Publish Event
                 ▼
        ┌───────────────────┐
        │  Azure Service Bus│
        │      orders       │
        └─────────┬─────────┘
                  │
                  ▼
        ┌─────────────────────┐
        │ Order Worker Service│
        └──────────┬──────────┘
                   │
          ┌────────┼─────────┐
          ▼        ▼         ▼
       Inventory  Payment   Notification

This is a very common microservice architecture.


16. Worker Service with Dependency Injection

Don't put all business logic inside:

Worker.cs

Instead:

Worker
  │
  ▼
OrderProcessor
  │
  ├── OrderRepository
  ├── InventoryService
  ├── PaymentService
  └── NotificationService

Example:

public interface IOrderProcessor
{
    Task ProcessAsync(
        OrderCreatedEvent order,
        CancellationToken cancellationToken);
}

Implementation:

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

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

    public async Task ProcessAsync(
        OrderCreatedEvent order,
        CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Processing Order {OrderId}",
            order.OrderId);

        // Business logic

        await Task.CompletedTask;
    }
}

Register:

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

17. Important Issue — Scoped Services

This is a very important interview question.

A Worker Service itself is generally effectively singleton-like because it lives for the lifetime of the host.

But services such as:

DbContext
Repository
UnitOfWork

are usually:

Scoped

You shouldn't inject a scoped DbContext directly into a long-lived singleton worker.

Instead use:

IServiceScopeFactory

Example:

public class OrderWorker : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;

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

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

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

            await processor.ProcessAsync(
                stoppingToken);

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

This creates a new DI scope for each processing cycle.


18. Worker + Entity Framework Core

For example:

public class OrderProcessor : IOrderProcessor
{
    private readonly ApplicationDbContext _db;

    public OrderProcessor(ApplicationDbContext db)
    {
        _db = db;
    }

    public async Task ProcessAsync(
        CancellationToken cancellationToken)
    {
        var orders = await _db.Orders
            .Where(x => x.Status == "Pending")
            .ToListAsync(cancellationToken);

        foreach (var order in orders)
        {
            order.Status = "Processed";
        }

        await _db.SaveChangesAsync(
            cancellationToken);
    }
}

Register:

builder.Services.AddDbContext<ApplicationDbContext>(
    options =>
        options.UseSqlServer(
            builder.Configuration.GetConnectionString(
                "DefaultConnection")));

19. Worker Service for Scheduled Processing

Workers aren't limited to queues.

You can also execute tasks periodically.

Example:

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

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

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

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

    private async Task GenerateReportAsync(
        CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Generating report at {Time}",
            DateTimeOffset.Now);

        await Task.CompletedTask;
    }
}

20. Worker Service for File Processing

Another real-world example:

Customer uploads CSV
       ↓
Blob Storage
       ↓
Queue
       ↓
Worker
       ↓
Read CSV
       ↓
Validate records
       ↓
Insert database
       ↓
Move file to Processed

This is excellent for Worker Services because processing can take several minutes.


21. Worker Service for Email Processing

Architecture:

Application
     │
     ▼
Email Queue
     │
     ▼
Email Worker
     │
     ├── Read message
     ├── Validate
     ├── Send email
     └── Complete message

This prevents email processing from slowing down the main API.


22. Worker Service in Microservices

Suppose your system contains:

Order Service
Payment Service
Inventory Service
Notification Service
Shipping Service

You can have:

Order API
     │
     ▼
Order Worker

Payment Worker

Inventory Worker

Notification Worker

Shipping Worker

Each worker can be independently deployed and scaled.

This follows an important microservice principle:

A microservice should own a specific business capability.


23. Scaling Workers

Suppose there are:

100 messages/hour

One worker might be enough.

But suppose:

100,000 messages/hour

You can run:

             Queue
               │
       ┌───────┼───────┐
       ▼       ▼       ▼
    Worker 1 Worker 2 Worker 3

In Kubernetes:

Deployment
     │
     ├── Pod 1
     ├── Pod 2
     ├── Pod 3
     └── Pod 4

Multiple workers consume messages from the same queue.

The broker distributes messages between consumers.


24. Worker Service + Kubernetes

A Worker can run as a container.

Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src

COPY . .

RUN dotnet restore

RUN dotnet publish \
    -c Release \
    -o /app/publish

FROM base AS final

WORKDIR /app

COPY --from=build /app/publish .

ENTRYPOINT ["dotnet", "OrderProcessing.Worker.dll"]

Then:

Docker Image
      ↓
Azure Container Registry
      ↓
AKS
      ↓
Worker Pod

25. Worker Deployment in Kubernetes

Example:

apiVersion: apps/v1
kind: Deployment

metadata:
  name: order-worker

spec:
  replicas: 3

  selector:
    matchLabels:
      app: order-worker

  template:
    metadata:
      labels:
        app: order-worker

    spec:
      containers:
        - name: order-worker

          image: myregistry/order-worker:1.0

          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"

            limits:
              cpu: "500m"
              memory: "512Mi"

Now Kubernetes runs:

Order Worker
   │
   ├── Pod 1
   ├── Pod 2
   └── Pod 3

26. Worker Service Failure Handling

Production workers must handle failures.

For example:

Worker
   ↓
Process Order
   ↓
Payment API
   ↓
FAILED

Don't simply crash the entire application.

Use:

Retry
 ↓
Retry
 ↓
Retry
 ↓
Still Failed
 ↓
Dead Letter Queue

For example:

Orders Queue
     │
     ▼
Worker
     │
     ├── Success → Complete
     │
     └── Failure
           │
           ▼
       Retry
           │
           ▼
      Max Retries
           │
           ▼
    Dead Letter Queue

27. Idempotency — Extremely Important

Imagine the worker receives:

OrderId = 1001

It processes it.

But before acknowledgement:

Worker crashes

The message may be delivered again.

Now:

Order 1001
     ↓
Processed
     ↓
Worker crashes
     ↓
Message delivered again
     ↓
Order 1001 processed again

Therefore your worker should be idempotent.

For example:

var alreadyProcessed =
    await _db.ProcessedMessages
        .AnyAsync(
            x => x.MessageId == messageId,
            cancellationToken);

if (alreadyProcessed)
{
    return;
}

Then:

Message
  ↓
Check MessageId
  ↓
Already processed?
  │
  ├── Yes → Skip
  │
  └── No → Process

This is extremely important in distributed systems.


28. Worker + Saga Pattern

Workers are also commonly used with Saga-based microservices.

Example:

Order Created
     ↓
Order Worker
     ↓
Payment
     ↓
Inventory
     ↓
Shipping

If inventory fails:

Order
  ↓
Payment SUCCESS
  ↓
Inventory FAILED
  ↓
Compensation
  ↓
Refund Payment
  ↓
Cancel Order

Workers can process these asynchronous commands/events.


29. Worker Service vs API

FeatureWeb APIWorker Service
HTTP endpointYesNo
Long-running processNot idealExcellent
Queue consumerPossibleExcellent
Scheduled jobsPossibleExcellent
Background processingLimitedExcellent
Request/responseYesNo
KubernetesYesYes
DockerYesYes
MicroservicesYesYes

30. Alternatives to Worker Services

Worker Services are not the only solution.

Depending on your requirement, you can use several alternatives.

1. Hangfire

Excellent for:

Background Jobs
Scheduled Jobs
Retries
Recurring Jobs
Dashboard

Architecture:

API
 ↓
Hangfire
 ↓
Background Job
 ↓
Database

Good when you need:

Run every day at 2 AM

or:

Run this job in background

31. Quartz.NET

Quartz.NET is useful for sophisticated scheduling.

Example:

Daily
Hourly
Weekly
Cron
Complex schedules

Architecture:

Application
     ↓
Quartz Scheduler
     ↓
Job
     ↓
Business Logic

Good for complex scheduling requirements.


32. Azure Functions

If you're already using Azure, Azure Functions can be an excellent alternative.

For example:

Service Bus
    ↓
Azure Function
    ↓
Process Order

Or:

Timer
 ↓
Azure Function
 ↓
Generate Report

Advantages:

  • Serverless

  • Automatic scaling

  • Event-driven

  • Less infrastructure management


33. Azure Service Bus Trigger

A common architecture:

Order API
     ↓
Azure Service Bus
     ↓
Azure Function
     ↓
Process Order

Instead of maintaining a continuously running Worker Service, Azure manages the execution environment.


34. Kubernetes CronJob

For batch jobs that run at a specific time:

Every night 2 AM
       ↓
Kubernetes CronJob
       ↓
Create Pod
       ↓
Execute job
       ↓
Pod completes

This is better than keeping a Worker continuously running for a job that only needs to execute once per day.


35. Azure Logic Apps

For workflow/integration scenarios:

Trigger
  ↓
Logic App
  ↓
Service A
  ↓
Service B
  ↓
Email

Useful for integration workflows rather than complex application business logic.


36. Azure Data Factory

For data movement/ETL:

SQL Server
     ↓
Azure Data Factory
     ↓
Transform
     ↓
Azure SQL

If your requirement is:

ETL
Data migration
Data integration
Scheduled data processing

ADF may be better than a Worker.


37. Azure WebJobs

For applications already hosted in Azure App Service, WebJobs can be useful for background execution.

Architecture:

Azure App Service
       │
       ├── Web API
       │
       └── WebJob

38. Which One Should You Choose?

A simple decision matrix:

RequirementRecommended
Long-running background processWorker Service
Queue consumerWorker Service / Azure Functions
Complex scheduled jobsQuartz.NET
Simple background jobsHangfire
Serverless event processingAzure Functions
Kubernetes scheduled batchCronJob
ETL/Data movementAzure Data Factory
Azure App Service background taskWebJobs
Complex integration workflowLogic Apps

39. Worker Service vs Azure Function

This is a common interview question.

Worker Service

You manage:
    Application
    Container/VM
    Deployment
    Scaling

Azure Function

Azure manages:
    Infrastructure
    Scaling
    Runtime

Worker:

Queue
 ↓
Worker
 ↓
Process

Function:

Queue
 ↓
Azure Function Trigger
 ↓
Process

40. Worker Service vs Hangfire

Worker Service

Better for:

Continuous processing
Queue consumers
Long-running workloads
Microservice background processes

Hangfire

Better for:

Scheduled jobs
Fire-and-forget jobs
Recurring jobs
Retry management
Job dashboard

41. Worker Service vs Kubernetes CronJob

Use Worker:

Continuous
 ↓
Consume messages
 ↓
Process continuously

Use CronJob:

2 AM
 ↓
Start
 ↓
Process
 ↓
Exit

For example:

Order queue consumer:

Worker Service

Daily database cleanup:

Kubernetes CronJob

42. Recommended Microservice Architecture

For a production e-commerce system, I'd typically consider:

                     ┌──────────────┐
                     │   Angular UI │
                     └──────┬───────┘
                            │
                            ▼
                    ┌───────────────┐
                    │ API Management│
                    └───────┬───────┘
                            │
            ┌───────────────┼────────────────┐
            ▼               ▼                ▼
       Order API       Payment API      Customer API
            │
            ▼
      Azure Service Bus
            │
     ┌──────┼─────────────┐
     ▼      ▼             ▼
 Order    Inventory    Notification
 Worker     Worker        Worker
     │        │             │
     ▼        ▼             ▼
 Azure     Azure          Email/
 SQL       SQL            SMS

And deploy using:

.NET Worker
     ↓
Docker
     ↓
Azure Container Registry
     ↓
AKS
     ↓
Pods
     ↓
Horizontal Scaling

43. Production Best Practices

When implementing Worker Services, remember these:

1. Use CancellationToken

await ProcessAsync(cancellationToken);

2. Use structured logging

_logger.LogInformation(
    "Processing Order {OrderId}",
    orderId);

3. Don't inject scoped dependencies directly

Use:

IServiceScopeFactory

4. Make processing idempotent

Prevent duplicate processing.

5. Implement retry

Transient failures should be retried.

6. Use Dead Letter Queue

Don't retry permanently bad messages forever.

7. Configure health monitoring

Monitor:

Worker status
Message count
Processing latency
Failure count
Retry count
DLQ count

8. Use distributed tracing

For microservices:

API
 ↓
Service Bus
 ↓
Worker
 ↓
Database

You should be able to trace the complete transaction/correlation flow.


44. Most Important Concept

The biggest idea to remember is:

Worker Services are not simply "another type of API." They are long-running background processes designed to execute work independently of incoming HTTP requests.

In microservices, they are particularly valuable for:

          EVENT
            │
            ▼
      Message Broker
            │
            ▼
     Worker Service
            │
     ┌──────┼──────┐
     ▼      ▼      ▼
    DB     API    Events

This allows your APIs to remain:

Fast
Stateless
Scalable
Responsive

while the Worker handles:

Long-running
Asynchronous
Retryable
Queue-based
Scheduled
Background

processing.

Recommended architecture for your .NET/Azure stack

Given a typical .NET + Microservices + Azure + Docker + AKS architecture, a strong production combination is:

                    CLIENT
                      │
                      ▼
                 API Management
                      │
                      ▼
                 .NET Web API
                      │
               Publish Event
                      │
                      ▼
              Azure Service Bus
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
     .NET Worker  .NET Worker  .NET Worker
       Order       Inventory    Notification
          │           │           │
          ▼           ▼           ▼
       Azure SQL   Azure SQL    External API

with:

Docker
  ↓
Azure Container Registry
  ↓
AKS
  ↓
Worker Pods
  ↓
Horizontal Scaling

That gives you a clean event-driven microservice architecture with independent scaling and asynchronous processing.

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.

Don't Copy

Protected by Copyscape Online Plagiarism Checker