Real-World Strategy Pattern in C# – Logging Example with Console, File, and Database

Introduction

The Strategy Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable.

In simpler words: instead of writing if-else or switch statements to select a behavior, you can swap the behavior dynamically at runtime without touching the client code.

This blog demonstrates a real-world example of the Strategy Pattern in C# / ASP.NET Core, using a logging system. We will dynamically choose between Console, File, and Database logging strategies via a web API.

By the end of this tutorial, you’ll see how to:

  • Use Strategy Pattern to decouple logging logic

  • Dynamically select a logging strategy at runtime

  • Build a scalable, maintainable backend API


When to Use the Strategy Pattern

The Strategy Pattern is useful in situations where behavior varies dynamically. Some common use cases include:

  • Logging: Console, File, Database, Email

  • Payment processing: Stripe, PayPal, Razorpay

  • File export: CSV, TXT, JSON, PDF

  • Sorting / Filtering: Dashboards and reports

  • Authentication / Authorization: Multiple providers or methods

In our POC, we focus on logging, which is a universal requirement in almost all backend systems.


Step 1: Define the Strategy Interface

The first step is to create a common interface for all logging strategies:namespace StrategyPattern.API.Strategies;

 

public interface ILoggerStrategyServcie
{
    public string Name { get; }
    public Task LogAsync(string message);
}

  • Name identifies the strategy (used for runtime selection)

  • LogAsync defines the logging behavior (async-friendly for real-world use)

Step 2: Implement Concrete Logging Strategies

Console Logger

public class ConsoleLoggerService : ILoggerStrategyServcie
{
    public string Name => "console";

    public Task LogAsync(string message)
    {
        if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));

        Console.WriteLine($"[{DateTime.UtcNow}]{message}");
        return Task.CompletedTask;
    }
}

File Logger 

public class FileLoggerService : ILoggerStrategyServcie
{
    public string Name => "file";
    private readonly string _filePath = "logs.txt";

    public Task LogAsync(string message)
    {
        if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));
        File.AppendAllText(_filePath, $"[{DateTime.UtcNow}] {message}" + Environment.NewLine);
        return Task.CompletedTask;
    }
}

Database Logger (Simulated) 

In production, DatabaseLoggerService would save logs to a database using EF Core or ADO.NET.

public class DatabaseLoggerService : ILoggerStrategyServcie
{
    public string Name => "db";

    public Task LogAsync(string message)
    {
        if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));
        Console.WriteLine($"[DB][{DateTime.UtcNow}] {message}");
        return Task.CompletedTask;
    }
}

Step 3: Create the Logger Resolver (Factory)

The Logger Resolver dynamically selects a logger based on a string key:

public class LoggerResolver
{
    private readonly IEnumerable<ILoggerStrategyServcie> _loggers;

    public LoggerResolver(IEnumerable<ILoggerStrategyServcie> loggers)
    {
        _loggers = loggers ?? throw new ArgumentNullException(nameof(loggers));
    }

    public ILoggerStrategyServcie Resolve(string name)
    {
        var logger = _loggers.FirstOrDefault(l => l.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
        return logger ?? throw new NotSupportedException($"Logger '{name}' not supported");
    }
}

Step 4: Create the Web API Controller
[Route("api/[controller]")]
[ApiController]
public class LogController : ControllerBase
{
    private readonly LoggerResolver _resolver;

    public LogController(LoggerResolver resolver)
    {
        _resolver = resolver;
    }

    [HttpPost]
    public async Task<IActionResult> Log(string message, string logger)
    {
        var strategy = _resolver.Resolve(logger);
        await strategy.LogAsync(message);
        return Ok("Logged successfully");
    }
}

Step 5: Configure Dependency Injection and Swagger

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// Register logging strategies
builder.Services.AddTransient<ILoggerStrategyServcie, ConsoleLoggerService>();
builder.Services.AddTransient<ILoggerStrategyServcie, FileLoggerService>();
builder.Services.AddTransient<ILoggerStrategyServcie, DatabaseLoggerService>();

// Register the resolver
builder.Services.AddSingleton<LoggerResolver>();

// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c => 
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Strategy Pattern Logging API V1")
    );
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

Step 6: Test the Logging API

POST /api/log?logger=console&message=HelloWorld
POST /api/log?logger=file&message=File log test
POST /api/log?logger=db&message=Database log test


Why This is a Real-World Design

  • Open/Closed Principle: Add new logging strategies without changing controller or resolver

  • Dependency Injection: Decouples logger creation from usage

  • Runtime Strategy Selection: Easily switch loggers dynamically

  • Extensible: Add EmailLogger, CloudLogger, or third-party logging in minutes


Future Enhancements

  • Add LogEntry with LogLevel, Source, CorrelationId for structured logging

  • Integrate real database logging (EF Core or ADO.NET)

  • Add async file or cloud logging

  • Include fallback strategies if a logger fails


Conclusion

The Strategy Pattern is a powerful tool for dynamic behavior selection in real-world apps.

This POC demonstrates:

  • How to structure logging with multiple strategies

  • How to make your code clean, scalable, and maintainable

  • How to apply design patterns in a web API context

By using Strategy Pattern, you no longer need messy if-else chains, and adding new loggers is trivial — perfect for enterprise systems.


GitHub Repository

I’ve shared this code in a GitHub repo:

Repo name and Link: StrategyPatternLogging