Monday, July 27, 2026

Command Design Pattern

 


Mastering Design Patterns in C# and ASP.NET Core

Part 4.2 – Command Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐⭐☆ Intermediate to Advanced
Prerequisites: C#, OOP, SOLID Principles, Interfaces, Dependency Injection, ASP.NET Core


Table of Contents

  1. Introduction

  2. What is the Command Design Pattern?

  3. Why Do We Need the Command Pattern?

  4. Command Pattern Terminology

  5. UML Class Diagram

  6. How the Command Pattern Works

  7. Complete C# Console Application

  8. Multiple Commands Example

  9. ASP.NET Core Implementation

  10. Command and Handler Architecture

  11. Command Pattern and CQRS

  12. Undo/Redo Functionality

  13. Queue and Background Processing

  14. Banking Transaction Example

  15. E-Commerce Order Example

  16. Real-World Enterprise Scenarios

  17. Advantages

  18. Disadvantages

  19. Best Practices

  20. Common Mistakes

  21. Command vs Strategy

  22. Command vs Mediator

  23. Command vs Chain of Responsibility

  24. Interview Questions

  25. Summary

  26. Coming Up Next


1. Introduction

In traditional application development, we often call a method directly:

orderService.CreateOrder();

This approach is perfectly fine for simple applications.

However, enterprise applications frequently need more than simply executing a method.

For example, an operation may need to be:

  • Logged

  • Validated

  • Queued

  • Stored

  • Retried

  • Executed asynchronously

  • Undone

  • Audited

  • Sent to another service

  • Processed through CQRS

This is where the Command Design Pattern becomes useful.

Instead of directly saying:

"Execute this method."

we create an object representing:

"Do this operation."

That object is called a Command.


2. What is the Command Design Pattern?

Definition

The Command Design Pattern is a behavioral design pattern that encapsulates a request or operation as an object.

In simple terms:

The Command Pattern turns a request into a standalone object.

This allows us to:

  • Pass requests around

  • Store requests

  • Queue requests

  • Log requests

  • Retry requests

  • Undo requests

  • Execute requests later

Instead of:

service.CreateOrder(order);

we can create:

CreateOrderCommand

and execute it through a handler.


3. Why Do We Need the Command Pattern?

Consider an e-commerce application.

Without Command Pattern:

public void CreateOrder(Order order)
{
    ValidateOrder(order);

    CheckInventory(order);

    ProcessPayment(order);

    SaveOrder(order);

    SendEmail(order);

    PublishEvent(order);
}

The operation is tightly coupled to the implementation.

Now imagine that tomorrow we need to:

  • Execute it asynchronously

  • Add retry logic

  • Put it on a queue

  • Log it

  • Undo it

  • Schedule it

  • Send it to another service

The design becomes more complicated.

With Command:

CreateOrderCommand
        ↓
Command Handler
        ↓
Order Service
        ↓
Database

The request itself becomes a first-class object.


4. Command Pattern Terminology

There are four major participants.

1. Command

Represents the operation/request.

Example:

CreateOrderCommand
UpdateCustomerCommand
CancelOrderCommand
ProcessPaymentCommand

2. Receiver

The actual object that knows how to perform the operation.

Example:

OrderService
PaymentService
CustomerService

3. Invoker

The object that tells the command to execute.

It doesn't need to know how the operation is implemented.

Example:

CommandInvoker
Queue
Controller
UI Button
Background Worker

4. Client

Creates the command and configures the necessary objects.

Example:

Controller
Application Service
UI

5. UML Class Diagram

The traditional Command Pattern can be represented as:

                    +----------------+
                    |     Client     |
                    +-------+--------+
                            |
                            | creates
                            ↓
                    +----------------+
                    |    Command     |
                    +----------------+
                    | + Execute()    |
                    +-------^--------+
                            |
             +--------------+--------------+
             |                             |
             |                             |
+--------------------------+   +--------------------------+
| ConcreteCommand          |   | ConcreteCommand          |
+--------------------------+   +--------------------------+
| - receiver               |   | - receiver               |
| + Execute()              |   | + Execute()              |
+------------+-------------+   +--------------------------+
             |
             | calls
             ↓
       +-------------+
       |  Receiver   |
       +-------------+
       | + Action()  |
       +-------------+

             ↑
             |
       +-------------+
       |   Invoker   |
       +-------------+
       | + Execute() |
       +-------------+

6. How the Command Pattern Works

The basic flow is:

Client
  ↓
Create Command
  ↓
Pass Command to Invoker
  ↓
Invoker calls Execute()
  ↓
Concrete Command
  ↓
Receiver
  ↓
Business Operation

For example:

Customer
   ↓
CreateOrderCommand
   ↓
Command Handler
   ↓
OrderService
   ↓
Database

The important point is:

The invoker does not need to know the internal details of the operation.


7. Complete C# Console Application

Let's create a simple banking example.

We want to support:

Deposit
Withdraw

Step 1 – Command Interface

public interface ICommand
{
    void Execute();
}

Step 2 – Receiver

The receiver performs the actual business operation.

public class BankAccount
{
    private decimal _balance;

    public BankAccount(decimal initialBalance)
    {
        _balance = initialBalance;
    }

    public void Deposit(decimal amount)
    {
        _balance += amount;

        Console.WriteLine(
            $"Deposited ${amount:N2}");

        Console.WriteLine(
            $"Current Balance: ${_balance:N2}");
    }

    public void Withdraw(decimal amount)
    {
        if (amount > _balance)
        {
            Console.WriteLine(
                "Insufficient balance.");

            return;
        }

        _balance -= amount;

        Console.WriteLine(
            $"Withdrawn ${amount:N2}");

        Console.WriteLine(
            $"Current Balance: ${_balance:N2}");
    }
}

Step 3 – Deposit Command

public class DepositCommand : ICommand
{
    private readonly BankAccount _account;
    private readonly decimal _amount;

    public DepositCommand(
        BankAccount account,
        decimal amount)
    {
        _account = account;
        _amount = amount;
    }

    public void Execute()
    {
        _account.Deposit(_amount);
    }
}

Step 4 – Withdraw Command

public class WithdrawCommand : ICommand
{
    private readonly BankAccount _account;
    private readonly decimal _amount;

    public WithdrawCommand(
        BankAccount account,
        decimal amount)
    {
        _account = account;
        _amount = amount;
    }

    public void Execute()
    {
        _account.Withdraw(_amount);
    }
}

Step 5 – Invoker

public class CommandInvoker
{
    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
    }
}

Step 6 – Client

class Program
{
    static void Main()
    {
        var account = new BankAccount(5000);

        var depositCommand =
            new DepositCommand(account, 1000);

        var withdrawCommand =
            new WithdrawCommand(account, 500);

        var invoker = new CommandInvoker();

        invoker.ExecuteCommand(depositCommand);

        invoker.ExecuteCommand(withdrawCommand);
    }
}

Output

Deposited $1,000.00
Current Balance: $6,000.00

Withdrawn $500.00
Current Balance: $5,500.00

Notice the separation:

Command
    ↓
Invoker
    ↓
Receiver

8. Multiple Commands Example

A real application can have many commands:

Order Commands
│
├── CreateOrderCommand
├── UpdateOrderCommand
├── CancelOrderCommand
├── ShipOrderCommand
└── CompleteOrderCommand

For example:

public interface ICommand
{
    void Execute();
}

Then:

public class CreateOrderCommand : ICommand
{
    public void Execute()
    {
        Console.WriteLine("Creating order...");
    }
}

And:

public class CancelOrderCommand : ICommand
{
    public void Execute()
    {
        Console.WriteLine("Cancelling order...");
    }
}

The invoker doesn't need separate logic for every command:

public void ExecuteCommand(ICommand command)
{
    command.Execute();
}

This is one of the important benefits of the pattern.


9. ASP.NET Core Implementation

Now let's convert the concept into a modern ASP.NET Core application.

Imagine an API endpoint:

POST /api/orders

The controller receives an HTTP request.

Instead of putting all business logic inside the controller:

Controller
    ↓
Business Logic
    ↓
Database

we can use:

Controller
    ↓
CreateOrderCommand
    ↓
Command Handler
    ↓
Order Service
    ↓
Repository
    ↓
Database

10. Command and Handler Architecture

A simple command could be:

public record CreateOrderCommand(
    int CustomerId,
    decimal TotalAmount);

The handler:

public class CreateOrderCommandHandler
{
    public async Task<int> Handle(
        CreateOrderCommand command)
    {
        Console.WriteLine(
            $"Creating order for customer {command.CustomerId}");

        // Business logic

        // Save to database

        await Task.CompletedTask;

        return 1001;
    }
}

Controller:

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly CreateOrderCommandHandler _handler;

    public OrdersController(
        CreateOrderCommandHandler handler)
    {
        _handler = handler;
    }

    [HttpPost]
    public async Task<IActionResult> Create(
        CreateOrderCommand command)
    {
        var orderId =
            await _handler.Handle(command);

        return Ok(new
        {
            OrderId = orderId
        });
    }
}

Register it:

builder.Services.AddScoped<
    CreateOrderCommandHandler>();

This approach keeps controllers thin and moves business operations into handlers.


11. Command Pattern and CQRS

This is one of the most important areas for .NET developers.

CQRS stands for:

Command Query Responsibility Segregation

CQRS separates:

Commands
    ↓
Change State

Queries
    ↓
Read State

Example:

                 Application
                      |
             +--------+--------+
             |                 |
          Command             Query
             |                 |
             ↓                 ↓
        Write Handler     Query Handler
             |                 |
             ↓                 ↓
        Write Database    Read Database

Commands might include:

CreateOrderCommand
UpdateOrderCommand
CancelOrderCommand
ApproveLoanCommand
ProcessPaymentCommand

Queries might include:

GetOrderByIdQuery
GetCustomerQuery
GetOrdersQuery

The Command Pattern provides a natural way to represent the write-side operations in CQRS.


12. Command Pattern with CQRS

A typical architecture:

Angular Application
       ↓
ASP.NET Core Controller
       ↓
Command
       ↓
Mediator
       ↓
Command Handler
       ↓
Domain Service
       ↓
Repository
       ↓
Database

For example:

POST /orders
     ↓
CreateOrderCommand
     ↓
CreateOrderCommandHandler
     ↓
OrderService
     ↓
OrderRepository
     ↓
SQL Server

This is very common in modern .NET enterprise applications.


13. Undo/Redo Functionality

One of the classic applications of Command is Undo/Redo.

Suppose a text editor supports:

Type
Delete
Copy
Paste

Each operation can be represented as a command.

TypeCommand
DeleteCommand
CopyCommand
PasteCommand

The application can store commands:

Command History

1. TypeCommand
2. TypeCommand
3. DeleteCommand
4. PasteCommand

Then Undo can reverse the most recent operation.


Example

public interface ICommand
{
    void Execute();
    void Undo();
}

Example:

public class AddTextCommand : ICommand
{
    private readonly TextEditor _editor;
    private readonly string _text;

    public AddTextCommand(
        TextEditor editor,
        string text)
    {
        _editor = editor;
        _text = text;
    }

    public void Execute()
    {
        _editor.AddText(_text);
    }

    public void Undo()
    {
        _editor.RemoveText(_text);
    }
}

Now commands can be stored in a history collection.

Stack<ICommand> history = new();

After executing:

command.Execute();

history.Push(command);

Undo:

var command = history.Pop();

command.Undo();

This is one of the classic reasons the Command Pattern is useful.


14. Queue and Background Processing

Commands can also be placed into queues.

Instead of:

HTTP Request
    ↓
Process Immediately

we can use:

HTTP Request
    ↓
Create Command
    ↓
Queue
    ↓
Background Worker
    ↓
Command Handler
    ↓
Business Logic

This is useful for long-running operations.

Examples:

  • Sending emails

  • Generating reports

  • Processing files

  • Image processing

  • Notifications

  • Batch processing

  • Order processing


Example Concept

Queue<ICommand> commandQueue = new();

commandQueue.Enqueue(
    new CreateOrderCommand(...));

A background worker can later process commands from the queue.

In production applications, you would typically use an appropriate durable messaging or background-processing mechanism rather than an in-memory Queue<T>.


15. Banking Transaction Example

Consider a banking system.

Operations may include:

Deposit
Withdraw
Transfer
PayBill
ProcessLoanPayment

Each can become a command:

DepositCommand
WithdrawCommand
TransferCommand
PayBillCommand
LoanPaymentCommand

Architecture:

Banking API
     ↓
Command
     ↓
Command Handler
     ↓
Banking Service
     ↓
Repository
     ↓
Database

For example:

POST /api/accounts/transfer
             ↓
TransferMoneyCommand
             ↓
TransferMoneyCommandHandler
             ↓
AccountService
             ↓
Debit Account
             ↓
Credit Account
             ↓
Transaction Record

This makes the transaction operation explicit and easier to test and audit.


16. E-Commerce Order Example

An e-commerce system might have:

CreateOrderCommand
UpdateOrderCommand
CancelOrderCommand
PayOrderCommand
ShipOrderCommand
DeliverOrderCommand

The architecture could look like:

Angular
   ↓
ASP.NET Core API
   ↓
Command
   ↓
Command Handler
   ↓
Domain Service
   ↓
Repository
   ↓
Database

For example:

CreateOrderCommand
        ↓
CreateOrderCommandHandler
        ↓
Validate Order
        ↓
Check Inventory
        ↓
Calculate Total
        ↓
Save Order
        ↓
Publish Event

This keeps the command handler focused on coordinating the use case.


17. Real-World Enterprise Scenarios

The Command Pattern can be used in:

Banking

  • Money transfers

  • Deposits

  • Withdrawals

  • Loan processing

  • Bill payments

E-Commerce

  • Create order

  • Cancel order

  • Ship order

  • Process payment

  • Refund payment

Customer Management

  • Create customer

  • Update customer

  • Delete customer

  • Change customer status

Background Processing

  • Generate report

  • Send email

  • Process document

  • Process batch

Distributed Systems

  • Message processing

  • Event-driven operations

  • Asynchronous commands

  • Retryable operations

CQRS

Commands represent state-changing operations.


18. Advantages

1. Decouples Sender and Receiver

The invoker doesn't need to know how the operation is implemented.


2. Supports Undo/Redo

Commands can store enough information to reverse an operation.


3. Supports Queuing

Commands can be stored and executed later.


4. Supports Logging

Because the operation is represented as an object, it can be logged.

CreateOrderCommand
CustomerId = 100
Amount = $250

5. Supports Retry

Commands can potentially be retried when processing fails.


6. Good for CQRS

Commands map naturally to state-changing operations.


7. Easier Testing

Command handlers can be tested independently.


19. Disadvantages

1. More Classes

A simple method:

CreateOrder();

could become:

CreateOrderCommand
CreateOrderCommandHandler
CreateOrderValidator

For small applications, this may be unnecessary complexity.


2. More Abstractions

The execution path becomes:

Controller
 ↓
Command
 ↓
Handler
 ↓
Service
 ↓
Repository

instead of directly calling a service.


3. Potential Boilerplate

Simple CRUD operations may not require a command abstraction.


4. Debugging Can Require More Navigation

Developers may need to follow:

Controller
 → Command
 → Handler
 → Service
 → Repository

20. Best Practices

1. Keep Commands Simple

A command should primarily contain the data required to perform an operation.

Example:

public record CreateCustomerCommand(
    string Name,
    string Email);

2. Keep Business Logic Out of Controllers

Prefer:

Controller
   ↓
Command
   ↓
Handler

rather than putting business logic directly in the controller.


3. One Command Should Represent One Business Operation

Good:

CreateOrderCommand

Avoid overly broad commands such as:

ProcessEverythingCommand

4. Keep Handlers Focused

A handler should coordinate a specific use case rather than becoming a giant business service.


5. Use Dependency Injection

Dependencies should be injected into handlers.

public class CreateOrderCommandHandler
{
    private readonly IOrderRepository _repository;

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

6. Make Commands Immutable Where Practical

Using C# records is often convenient:

public record CreateOrderCommand(
    int CustomerId,
    decimal Amount);

This reduces accidental modification after creation.


21. Common Mistakes

Mistake 1 – Putting Business Logic Inside the Command

Avoid:

public class CreateOrderCommand
{
    public void Execute()
    {
        // 500 lines of business logic
    }
}

Prefer:

Command
   ↓
Handler
   ↓
Business Logic

Mistake 2 – Creating Commands for Every Tiny Method

Not every method needs a command.

Use the pattern when the abstraction provides real value.


Mistake 3 – Creating Giant Handlers

Avoid:

OrderCommandHandler
    ↓
Create
Update
Delete
Cancel
Ship
Refund
Return

Consider separate commands for separate business operations.


Mistake 4 – Mixing Queries and Commands

In a CQRS-oriented architecture, keep read and write responsibilities clear.


Mistake 5 – Using Commands Without a Reason

Don't use Command merely because it is a design pattern.

Ask:

Does representing this operation as an object provide a meaningful architectural benefit?


22. Command vs Strategy

These patterns can look similar because both use interfaces and implementations.

But their intent is different.

FeatureCommandStrategy
Primary PurposeEncapsulate a requestEncapsulate an algorithm
FocusWhat should be done?How should it be done?
Can be Queued?YesUsually not the main goal
Undo/RedoCommon use caseNot typical
CQRSCommonNot primary
Algorithm SelectionNot primaryCore purpose

Example

Command:

CreateOrderCommand

Strategy:

IPaymentStrategy
 ├── CreditCardStrategy
 ├── BankTransferStrategy
 └── WalletStrategy

Think:

Command = What operation should happen?

Strategy = Which algorithm should perform it?


23. Command vs Mediator

Command and Mediator are often used together.

Command

Represents the request:

CreateOrderCommand

Mediator

Routes the command to its handler:

CreateOrderCommand
       ↓
Mediator
       ↓
CreateOrderCommandHandler

So:

Command = Request

Mediator = Communication/Dispatch Mechanism

In modern .NET applications, a mediator-style architecture can help avoid controllers directly depending on many application services.


24. Command vs Chain of Responsibility

Both are behavioral patterns, but they solve different problems.

FeatureCommandChain of Responsibility
Main GoalEncapsulate a requestPass request through handlers
Primary FocusRepresent an operationProcess through a sequence
QueueVery commonNot the primary purpose
Undo/RedoCommonNot typical
Handler ChainNot requiredCore concept
CQRSCommonNot primary

Example:

Command:

CreateOrderCommand
       ↓
CreateOrderHandler

Chain:

Request
   ↓
Validation
   ↓
Authentication
   ↓
Authorization
   ↓
Processing

25. Interview Questions

Beginner Level

1. What is the Command Design Pattern?

The Command Pattern encapsulates a request as an object, allowing the request to be passed, stored, queued, logged, and executed independently from the sender.


2. What are the main components?

The traditional components are:

  • Client

  • Command

  • Concrete Command

  • Receiver

  • Invoker


3. What is a Receiver?

The Receiver contains the actual business logic that performs the requested operation.


4. What is an Invoker?

The Invoker triggers the command without needing to know how the operation is performed.


5. Why is Command a behavioral pattern?

Because it focuses on encapsulating and controlling behavior or operations rather than object creation or structure.


Intermediate Level

6. What are the real-world applications?

Common examples include:

  • CQRS

  • Undo/Redo

  • Queues

  • Background processing

  • Transaction processing

  • Auditing

  • Logging

  • Retryable operations


7. How does Command support CQRS?

Commands represent operations that change application state, while queries retrieve data without changing state.


8. What is the difference between Command and Strategy?

Command represents an operation/request.

Strategy represents an interchangeable algorithm.


9. What is the difference between Command and Mediator?

Command represents the request.

Mediator provides a mechanism for dispatching or coordinating communication between the sender and handler.


10. Can Commands be asynchronous?

Absolutely.

For ASP.NET Core applications, a command handler commonly exposes:

Task<TResult>

For example:

public async Task<int> Handle(
    CreateOrderCommand command)
{
    // Async database operation
    return 1001;
}

Advanced / Architect Level

11. Why are Commands useful in distributed systems?

Commands can represent business operations that can be:

  • Serialized

  • Placed on queues

  • Processed asynchronously

  • Retried

  • Logged

  • Audited

This makes them useful in event-driven and message-driven architectures.


12. Should every API endpoint use Command Pattern?

No.

The pattern should be introduced when it provides meaningful benefits such as:

  • Complex use cases

  • CQRS

  • Asynchronous processing

  • Command routing

  • Auditing

  • Undo/Redo

  • Message processing

For simple applications, direct service calls may be more appropriate.


13. What is the relationship between Command and Command Handler?

The command contains the request data.

The handler contains the application logic required to execute that request.

Command
   ↓
"Create this order"

Handler
   ↓
"Here is how we create the order"

14. How can Commands support idempotency?

In distributed systems, a command can contain a unique request or idempotency identifier.

For example:

public record CreatePaymentCommand(
    Guid RequestId,
    int AccountId,
    decimal Amount);

The handler can check whether the RequestId has already been processed before performing the operation again.

This is particularly important when commands are retried.


15. How can Command Pattern be used with Azure Service Bus or RabbitMQ?

A command can be serialized and published to a messaging system:

API
 ↓
Command
 ↓
Message Broker
 ↓
Consumer
 ↓
Command Handler
 ↓
Business Logic

This enables asynchronous processing and decouples the producer from the consumer.


26. Command Pattern in a Modern .NET Architecture

A practical enterprise architecture might look like this:

                    Angular Application
                            |
                            ↓
                    ASP.NET Core API
                            |
                            ↓
                     Controller
                            |
                            ↓
                  CreateOrderCommand
                            |
                            ↓
                        Mediator
                            |
                            ↓
               CreateOrderCommandHandler
                            |
              +-------------+-------------+
              ↓                           ↓
        Domain Service              Validation
              |
              ↓
        Order Repository
              |
              ↓
           SQL Server

For asynchronous processing:

Angular
   ↓
ASP.NET Core API
   ↓
Command
   ↓
Azure Service Bus / RabbitMQ
   ↓
Consumer
   ↓
Command Handler
   ↓
Business Logic
   ↓
Database

This is where the Command Pattern becomes particularly powerful in enterprise applications.


27. Command Pattern – Key Takeaways

Remember these five points:

1. Command represents an operation

CreateOrderCommand

2. Handler executes the operation

CreateOrderCommandHandler

3. Receiver contains the actual business capability

OrderService

4. Commands can be stored, queued, logged, and retried

Command
   ↓
Queue
   ↓
Worker
   ↓
Handler

5. Command is a major building block for CQRS

Command
   ↓
Handler
   ↓
Write Model

Conclusion

The Command Design Pattern transforms an operation into an object.

Instead of tightly coupling a caller to the operation:

Caller → Service Method

we can create a more flexible architecture:

Caller
  ↓
Command
  ↓
Handler
  ↓
Receiver / Domain Service

This separation enables powerful capabilities such as:

  • CQRS

  • Undo/Redo

  • Queue-based processing

  • Background jobs

  • Auditing

  • Logging

  • Retry mechanisms

  • Asynchronous processing

  • Distributed command processing

For modern C# and ASP.NET Core applications, the Command Pattern is particularly valuable when an application has complex business operations, multiple execution paths, asynchronous workflows, or CQRS-based architecture.

The key idea: Encapsulate "what needs to be done" as an object, separate it from "how it is done," and gain the flexibility to execute, queue, log, retry, or undo that operation.


🚀 Coming Up Next: Part 4.3 – Interpreter Design Pattern

In the next article, we'll explore the Interpreter Design Pattern, including:

  • What is the Interpreter Pattern?

  • Why do we need it?

  • Expression and Grammar concepts

  • Terminal and Non-Terminal Expressions

  • UML Class Diagram

  • Complete C# Console Application

  • Building a simple expression interpreter

  • ASP.NET Core implementation

  • Business rule evaluation

  • Search/filter expression example

  • Real-world enterprise scenarios

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interpreter vs Strategy

  • Interpreter vs Composite

  • Interpreter vs Specification Pattern

  • Interview questions

We'll also examine why the Interpreter Pattern is useful for rule engines, expression evaluation, filtering, query languages, and configurable business rules, and how it relates to expression trees and modern .NET applications.

No comments:

Don't Copy

Protected by Copyscape Online Plagiarism Checker