Updated on 5 min read Manikandan Design Patterns

Factory Method Design Pattern in .NET Core API

A practical look at the Factory Method pattern in C#, with a clear example and guidance on when to use it in an ASP.NET Core API.

A practical look at the Factory Method pattern in C#, with a clear example and guidance on when to use it in an ASP.NET Core API.

Intro

When I first started working on APIs, I noticed a pattern cropping up repeatedly: creating objects based on some condition. Instead of scattering new calls everywhere and tangling creation logic into business logic, the Factory pattern gives that responsibility a proper home.

What Is the Factory Method Pattern?

Factory Method is a creational design pattern that centralizes object creation behind a method. Instead of calling new directly throughout the codebase, you ask a factory method to create the right object for you based on the input or context you provide.

In plain terms, the caller does not need to know which class to instantiate. It just describes what it needs, and the factory handles the rest.

Factory Class Example

The scenario below shows a notification system that supports multiple channels. The factory decides which notifier to create based on the channel type.

public interface INotification
{
void Send(string message);
}
public class EmailNotification : INotification
{
public void Send(string message) =>
Console.WriteLine($"[Email] {message}");
}
public class SmsNotification : INotification
{
public void Send(string message) =>
Console.WriteLine($"[SMS] {message}");
}
public class PushNotification : INotification
{
public void Send(string message) =>
Console.WriteLine($"[Push] {message}");
}

Here, How we can implement those

public enum NotificationChannel
{
Email,
Sms,
Push
}
public static class NotificationFactory
{
public static INotification Create(NotificationChannel channel) =>
channel switch
{
NotificationChannel.Email => new EmailNotification(),
NotificationChannel.Sms => new SmsNotification(),
NotificationChannel.Push => new PushNotification(),
_ => throw new ArgumentOutOfRangeException(nameof(channel), "Unknown channel")
};
}

Usage

var notifier = NotificationFactory.Create(NotificationChannel.Email);
notifier.Send("Your order has been placed.");

Why This Works

Single Creation Point

All object creation goes through the factory, so changes to construction logic stay in one place.

Loose Coupling

The caller depends only on the INotification interface, not on any concrete class.

Easy Extension

Adding a new channel means adding a new class and one line in the factory, with no changes to existing callers.

When to Use It

Factory is a good fit when the exact type of object to create is not known until runtime.

  • creating different payment processors based on provider
  • choosing a storage backend based on configuration
  • producing different report formats based on user selection
  • wiring up strategy objects without exposing concrete types

That said, if you only ever create one type of object, a factory adds more structure than value. Use it when the variation is real.

Important Note for ASP.NET Core

In ASP.NET Core, you can register a factory delegate directly with the DI container to let it resolve the right implementation at runtime.

builder.Services.AddTransient<EmailNotification>();
builder.Services.AddTransient<SmsNotification>();
builder.Services.AddTransient<PushNotification>();
builder.Services.AddTransient<Func<NotificationChannel, INotification>>(sp => channel =>
channel switch
{
NotificationChannel.Email => sp.GetRequiredService<EmailNotification>(),
NotificationChannel.Sms => sp.GetRequiredService<SmsNotification>(),
NotificationChannel.Push => sp.GetRequiredService<PushNotification>(),
_ => throw new ArgumentOutOfRangeException(nameof(channel), "Unknown channel")
});

Then inject and use it in a service:

public class OrderService(Func<NotificationChannel, INotification> notificationFactory)
{
public void Notify(NotificationChannel channel, string message)
{
var notifier = notificationFactory(channel);
notifier.Send(message);
}
}

This keeps the factory logic testable and lets the container manage lifetimes cleanly.

Core Components of the Pattern

PartPurposeExample in this article
Product interfaceDefines what the factory producesINotification
Concrete productsThe actual implementationsEmailNotification, SmsNotification, PushNotification
FactoryCreates and returns the correct productNotificationFactory
CallerAsks the factory for an objectOrderService

Common Factory Variations

You will usually see the Factory Method pattern in these three forms:

SimpleFactory

This is the most beginner-friendly version. A static method chooses the concrete type and returns it as an abstraction.

ParameterizedFactory

This version decides creation using multiple runtime inputs and business rules, making the factory behavior more dynamic.

VirtualConstructor

This is the closest form to the classic GoF Factory Method. A base creator defines the workflow, and subclasses override the factory method to choose the concrete product.

How the Variations Differ

VariationCreation approachExtensibilityBest use case
SimpleFactoryOne static factory method creates based on a simple inputLow to MediumIntro to centralized object creation
ParameterizedFactoryFactory decides using multiple parameters and rulesMediumRuntime rule-based object selection
VirtualConstructorBase workflow calls an overridable factory methodHighTrue Factory Method with polymorphism

SOLID Principles Behind It

SRP — Single Responsibility Principle

The factory class has one job: deciding which object to create. Business logic stays in the products, not mixed into the creation code.

OCP — Open/Closed Principle

You can add a new notification channel by adding a new class and registering it, without modifying existing callers or products.

LSP — Liskov Substitution Principle

Because all products implement INotification, any concrete type can be used wherever the interface is expected without breaking behavior.

DIP — Dependency Inversion Principle

Callers depend on the INotification abstraction, not on EmailNotification or any other concrete class.

UML Diagram

Factory Method UML diagram showing Client, NotificationFactory, INotification, and concrete notification classes.

A Quick Usage Example

INotification email = NotificationFactory.Create(NotificationChannel.Email);
INotification sms = NotificationFactory.Create(NotificationChannel.Sms);
email.Send("Your invoice is ready.");
sms.Send("Your OTP is 482910.");

Each call returns a different concrete type, but the calling code treats them identically through the interface.

How to Validate the Pattern

If you want to confirm your factory is working correctly, check these points:

  • the factory should return a different concrete type based on the input
  • the caller should only hold a reference to the interface, never to a concrete class
  • adding a new product type should require no changes to existing callers
  • the factory should throw a clear error for unknown input rather than returning null

A quick practical check is to pass each valid channel value into the factory and confirm the returned object behaves as expected. For multi-threaded scenarios, verify that the factory handles concurrent calls safely.

Is there any other way to check our implementations

  • Yes, we can write test cases for those implementations
  • Here is the link, to test using unit test cases

Summary

The Factory Method pattern centralizes object creation behind a single point, decouples callers from concrete types through an interface, and makes the codebase easier to extend when new product types are introduced. It is one of the most practical patterns to reach for whenever construction logic depends on runtime conditions.

Share:
Back to Blog

Related Posts

View All Posts »