0Pricing
C# Academy · Lesson

Factory & Options Patterns

Use factory delegates, IServiceProvider, and the Options pattern to handle conditional or configurable dependencies.

Factory & Options Patterns is a free C# Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

When Constructor Injection Isn't Enough

Sometimes you need to create services conditionally, based on runtime data, or configure them differently per use case. That's where factory patterns and the Options pattern shine.

Factory Delegates with AddTransient

You can pass a factory delegate to AddTransient, AddScoped, or AddSingleton. The delegate receives IServiceProvider so you can resolve other services.

builder.Services.AddTransient<IPaymentGateway>(sp =>
{
    var config = sp.GetRequiredService<IOptions<PaymentConfig>>().Value;
    return config.Provider == "stripe"
        ? new StripeGateway(config.ApiKey)
        : new PayPalGateway(config.ClientId, config.Secret);
});

Named Options with IOptionsSnapshot

The Options pattern binds configuration sections to strongly-typed classes. Use IOptions<T> for singletons and IOptionsSnapshot<T> for per-request refreshed values.

public class SmtpSettings
{
    public string Host { get; set; } = "";
    public int Port { get; set; } = 587;
    public string Username { get; set; } = "";
}

// Registration
builder.Services.Configure<SmtpSettings>(
    builder.Configuration.GetSection("Smtp"));

// Consumption
public class EmailService
{
    private readonly SmtpSettings _settings;
    public EmailService(IOptions<SmtpSettings> opts)
        => _settings = opts.Value;
}

IOptionsMonitor for Hot Reload

IOptionsMonitor<T> provides the current option value at the time of access and notifies you when configuration changes — perfect for Singletons that need live config updates.

public class FeatureFlagService
{
    private readonly IOptionsMonitor<FeatureFlags> _monitor;

    public FeatureFlagService(IOptionsMonitor<FeatureFlags> monitor)
        => _monitor = monitor;

    public bool IsEnabled(string flag)
    {
        // Always reads the latest config value
        return _monitor.CurrentValue.Flags.GetValueOrDefault(flag);
    }
}

Named Options

Named options let you register multiple configurations of the same type. Use Configure<T>(name, ...) and resolve with IOptionsSnapshot<T>.Get(name).

builder.Services.Configure<S3Settings>("primary",
    builder.Configuration.GetSection("S3:Primary"));
builder.Services.Configure<S3Settings>("backup",
    builder.Configuration.GetSection("S3:Backup"));

public class S3Service
{
    public S3Service(IOptionsSnapshot<S3Settings> opts)
    {
        var primary = opts.Get("primary");
        var backup  = opts.Get("backup");
    }
}

Validating Options at Startup

Use ValidateDataAnnotations() or a custom validator to ensure configuration is correct before the app starts. Combine with ValidateOnStart() for fail-fast behavior.

public class SmtpSettings
{
    [Required] public string Host { get; set; } = "";
    [Range(1, 65535)] public int Port { get; set; } = 587;
}

builder.Services
    .AddOptions<SmtpSettings>()
    .Bind(builder.Configuration.GetSection("Smtp"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Abstract Factory Pattern

An abstract factory interface lets you inject a factory into services, delaying construction until runtime data is available — without taking a direct dependency on IServiceProvider.

public interface IReportFactory
{
    IReport Create(string reportType);
}

public class ReportFactory : IReportFactory
{
    private readonly IServiceProvider _sp;
    public ReportFactory(IServiceProvider sp) => _sp = sp;

    public IReport Create(string reportType) => reportType switch
    {
        "pdf"  => _sp.GetRequiredService<PdfReport>(),
        "excel"=> _sp.GetRequiredService<ExcelReport>(),
        _      => throw new ArgumentException("Unknown type")
    };
}

builder.Services.AddTransient<IReportFactory, ReportFactory>();

Keyed Services (.NET 8)

.NET 8 introduces keyed services: register multiple implementations under different keys and resolve the right one with [FromKeyedServices] or GetKeyedService.

builder.Services.AddKeyedScoped<IPaymentGateway, StripeGateway>("stripe");
builder.Services.AddKeyedScoped<IPaymentGateway, PayPalGateway>("paypal");

// Resolve in a class:
public class CheckoutService(
    [FromKeyedServices("stripe")] IPaymentGateway stripe,
    [FromKeyedServices("paypal")]  IPaymentGateway paypal) { }

PostConfigure for Overrides

PostConfigure runs after all Configure calls. Use it to apply overrides in tests or to enforce invariants regardless of what configuration was loaded.

// In integration tests: force test values after real config
builder.Services.PostConfigure<SmtpSettings>(opts =>
{
    opts.Host = "smtp.test.local";
    opts.Port = 25;
});

IServiceCollection Extension Methods

Package your registrations into extension methods to keep Program.cs clean and make modules reusable across projects.

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddPaymentServices(
        this IServiceCollection services,
        IConfiguration config)
    {
        services.Configure<PaymentConfig>(config.GetSection("Payment"));
        services.AddScoped<IPaymentGateway, StripeGateway>();
        services.AddScoped<PaymentService>();
        return services;
    }
}

// Usage in Program.cs:
builder.Services.AddPaymentServices(builder.Configuration);

Real-World: Multi-Provider Notification

Using a factory and named options together, you can select the right notification provider at runtime based on user preference stored in the database.

builder.Services.AddKeyedScoped<INotifier, EmailNotifier>("email");
builder.Services.AddKeyedScoped<INotifier, SmsNotifier>("sms");
builder.Services.AddKeyedScoped<INotifier, PushNotifier>("push");

public class NotificationService
{
    private readonly IServiceProvider _sp;
    public NotificationService(IServiceProvider sp) => _sp = sp;

    public Task SendAsync(string channel, string message)
    {
        var notifier = _sp.GetRequiredKeyedService<INotifier>(channel);
        return notifier.SendAsync(message);
    }
}

Quick Check

Which interface provides the current options value AND notifies on configuration changes, making it suitable for Singleton services?

Recap: Factory & Options Patterns

Key takeaways:

  • Factory delegates in Add* methods enable conditional or runtime-configured service creation
  • Options pattern binds config sections to strongly typed POCOs
  • IOptions = static, IOptionsSnapshot = per-request, IOptionsMonitor = live updates
  • Validate options at startup with ValidateDataAnnotations().ValidateOnStart()
  • Keyed services (.NET 8) replace factory workarounds for named implementations

Frequently asked questions

Is the “Factory & Options Patterns” lesson free?

Yes — the full text of “Factory & Options Patterns” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Factory & Options Patterns”?

Use factory delegates, IServiceProvider, and the Options pattern to handle conditional or configurable dependencies. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Factory & Options Patterns” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. DI Container Fundamentals
  2. Service Lifetimes: Transient, Scoped, Singleton
  3. Constructor Injection & Interfaces
  4. Factory & Options Patterns
← Back to C# Academy