Skip to main content

Command Palette

Search for a command to run...

Keyed Services vs Factory Pattern vs Named Services in ASP.NET Core: Which DI Strategy Should Your .NET Team Use in 2026?

Resolve multiple implementations cleanly: a practical comparison for enterprise .NET teams choosing between Keyed Services, Factory Pattern, and Named Services in ASP.NET Core.

Published
โ€ข12 min read
Keyed Services vs Factory Pattern vs Named Services in ASP.NET Core: Which DI Strategy Should Your .NET Team Use in 2026?

Managing multiple implementations of the same interface is one of the most common architectural decisions teams face when building .NET APIs. Before .NET 8, the standard approaches were the Factory Pattern or home-grown named service abstractions. With the introduction of Keyed Services in .NET 8, teams now have a first-class, built-in DI mechanism for resolving named registrations. Choosing the right strategy โ€” Keyed Services, the Factory Pattern, or a custom named service abstraction โ€” has real consequences for maintainability, testability, and runtime correctness in enterprise ASP.NET Core applications.


๐ŸŽ Want implementation-ready .NET source code you can drop straight into your project? Join Coding Droplets on Patreon for exclusive tutorials, premium code samples, and early access to new content. ๐Ÿ‘‰ https://www.patreon.com/CodingDroplets


What Problem Are We Actually Solving?

The core challenge: you have a single interface โ€” say INotificationService โ€” but you need to resolve different concrete implementations depending on context. Maybe one implementation sends email, another sends SMS, and a third pushes to a mobile device. The calling code needs to pick the right one at runtime based on business logic.

This is the "multiple implementations of the same interface" problem, and it shows up constantly in enterprise .NET systems:

  • Payment gateways (Stripe, PayPal, Braintree)
  • Storage providers (Azure Blob, S3, local disk)
  • Messaging channels (email, SMS, push notification)
  • Report exporters (PDF, CSV, Excel)

Every option in this article is a valid way to solve it. The differences lie in boilerplate, coupling, testability, and how well they scale as the system grows.


Overview: The Three Strategies

Keyed Services (Built-In Since .NET 8)

Keyed Services are a first-party DI feature introduced in .NET 8. They allow you to register multiple implementations of the same interface under different keys, and then resolve them explicitly by key using [FromKeyedServices] attribute injection or IServiceProvider.GetRequiredKeyedService<T>(key).

The registration looks like:

services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");

And resolution via constructor injection:

public MyController([FromKeyedServices("email")] INotificationService emailService) { ... }

Keyed Services are directly integrated into the built-in Microsoft DI container. No third-party libraries. No additional packages. No wrapper types.

The Factory Pattern

The Factory Pattern predates .NET 8 Keyed Services and remains a widely used approach. A factory class (or delegate) is registered in DI and is responsible for instantiating and returning the correct implementation at runtime.

The factory encapsulates the selection logic. Consumer code takes a dependency on INotificationServiceFactory rather than INotificationService directly. The factory resolves the appropriate implementation based on a parameter or enum value.

This approach works with any version of .NET Core / .NET 5+, is familiar to developers across ecosystems, and offers a natural place to centralise selection logic.

Named Services Pattern (Custom Abstraction)

The Named Services Pattern is a convention-based workaround that teams adopted before Keyed Services existed. It typically involves wrapping a dictionary or registry of implementations into a custom interface:

IEnumerable<INotificationService> + a resolver/registry

Or it involves creating a typed service locator โ€” a dictionary-backed class that maps a key (string or enum) to the concrete service. It is effectively a DIY version of what Keyed Services now provide out of the box.


Side-by-Side Comparison

Dimension Keyed Services Factory Pattern Named Services
Built-in support โœ… .NET 8+ native โŒ Manual setup โŒ Manual setup
Registration boilerplate Low Medium High
Runtime selection โœ… Supported via provider โœ… Full control โœ… Full control
Constructor injection โœ… [FromKeyedServices] โŒ Requires factory dep โŒ Requires registry dep
Testability โœ… Mock by key โœ… Mock factory โš ๏ธ Complex mocking
Compile-time safety โš ๏ธ Keys are stringly-typed โœ… Enums possible โš ๏ธ Stringly-typed
Blazor/gRPC/SignalR compatibility โš ๏ธ Limited outside MVC/MinimalAPI โœ… Universal โœ… Universal
.NET version requirement .NET 8+ only Any .NET Any .NET
Works with third-party DI (Autofac etc) โš ๏ธ Not all support it โœ… Universal โœ… Universal
Complexity ceiling Medium Low-Medium High

When to Use Keyed Services

Keyed Services are the right choice when:

You are on .NET 8 or newer and own the DI container. The built-in Microsoft DI container fully supports Keyed Services. If your application is .NET 8+, there is no reason to build a factory wrapper just to resolve multiple implementations.

The selection key is known at composition time or injection point. If a specific endpoint always uses a specific implementation (e.g., an endpoint dedicated to email notifications always takes the email service), [FromKeyedServices] in the constructor is clean and explicit.

You want to reduce boilerplate without introducing a custom factory. Keyed Services replace 20-40 lines of factory code with 2-3 registration lines and an attribute.

You are building Minimal API endpoints. Keyed Services integrate cleanly with app.MapGet parameter binding via [FromKeyedServices].

Avoid Keyed Services when:

  • Your team is on .NET 6 or .NET 7 (not available natively)
  • You use a third-party DI container like Autofac or Lamar as a replacement (support varies)
  • The selection key is deeply business-logic-driven and needs runtime computation across multiple conditions โ€” factory pattern is cleaner here
  • You need to enumerate all registered implementations (the factory or IEnumerable<T> approach is better suited)

When to Use the Factory Pattern

The Factory Pattern remains the right tool when:

You need runtime selection based on complex business logic. If choosing the correct implementation requires evaluating a database value, user role, tenant config, or feature flag โ€” a factory with injected dependencies and selection logic is cleaner and more testable than key-based strings.

You need to support .NET 6/7. If your team is not yet on .NET 8, the factory is the standard enterprise approach.

You use a third-party DI container. Autofac, Lamar, Simple Injector โ€” all support factories cleanly. Keyed Service compatibility varies.

You want a strict enum-based contract. Factories can enforce strongly-typed enums as selection tokens, eliminating stringly-typed mistakes that Keyed Services (by default) expose.

You need to centralise instance lifecycle logic. If different implementations have different creation requirements, pooling, or warm-up logic, a factory is the right encapsulation boundary.

Avoid the Factory Pattern when:

  • The selection key is static and known at injection โ€” Keyed Services eliminate the extra indirection
  • You find yourself writing the same factory boilerplate across every feature module in a .NET 8 project

When to Use the Named Services Pattern

The Named Services Pattern (custom registry/resolver) is rarely the first choice in 2026. It made sense when neither Keyed Services nor a clean factory convention existed. In practice, most teams used it as a stepping stone.

Use it when:

  • You have an existing codebase on .NET 5/6 with this pattern already in place โ€” migration cost exceeds benefit
  • You need to build a plugin system where external contributors register named services and your core library discovers them โ€” a custom registry may be more flexible than DI keys

Avoid the Named Services Pattern for new projects. The combination of higher boilerplate, harder testability, and the availability of Keyed Services and the Factory Pattern makes it the weakest starting point for greenfield work.


Is the Factory Pattern Dead?

Not at all. The "death of the factory pattern" framing that circulated when .NET 8 launched overstated things. The factory pattern solves a different problem at a different layer.

Keyed Services solve: "I always want the email service at this injection point." Factory Pattern solves: "Evaluate this user's subscription tier, region, and feature flags, then return the appropriate payment provider."

They are complementary. In a well-structured .NET 8 application you might use Keyed Services for static, point-of-injection selections and the Factory Pattern for dynamic, business-rule-driven selections.


What Does the SERP Miss? A Content Gap Worth Filling

Most articles covering this topic focus on the mechanics of registration. Few address the enterprise decision points:

  • Testability at scale. Keyed Services mock cleanly in unit tests with Mock<INotificationService> registered under a key. Factory Pattern tests require mocking the factory itself. Named Services with dictionaries require populating the dictionary correctly in test setup. Keyed Services and Factory Pattern are roughly equal here; Named Services lag behind.

  • OpenTelemetry and diagnostics. If you are tracing which implementation was selected, factory methods give you a natural instrumentation point. Keyed Services resolved implicitly at injection time leave less tracing surface area.

  • Multi-tenant systems. In multi-tenant ASP.NET Core apps, per-tenant service resolution is a common requirement. The Factory Pattern, with access to IHttpContextAccessor or a tenant context service, handles this well. Keyed Services require the key to be known at the injection point โ€” which in a multi-tenant scenario means you likely need a factory to retrieve the key first anyway.


Decision Matrix: Which One for Your Team?

Scenario Recommended Strategy
New .NET 8+ project, simple multi-impl โœ… Keyed Services
.NET 6/7 project โœ… Factory Pattern
Runtime selection from DB/config โœ… Factory Pattern
Multi-tenant, per-request selection โœ… Factory Pattern
Plugin/extensibility system โœ… Named Services or Factory
Existing codebase with Named Services โœ… Migrate to Keyed Services on .NET 8
Third-party DI container (Autofac) โœ… Factory Pattern
Minimal API parameter injection โœ… Keyed Services

Anti-Patterns to Avoid

The Service Locator anti-pattern. All three approaches can be abused by passing IServiceProvider directly into classes and calling GetRequiredService. This hides dependencies and makes testing painful. Prefer constructor injection with [FromKeyedServices] or typed factory interfaces.

Over-keying. Registering dozens of implementations under string keys invites typos and runtime failures. Use enum-backed constants or static string fields for keys to keep them maintainable.

Factory explosion. One factory per feature module, all doing essentially the same string switch, is a sign you need Keyed Services instead.

Ignoring service lifetimes. A keyed AddKeyedSingleton service that holds scoped state, or a factory that creates scoped services and stores them in a singleton โ€” both introduce subtle lifecycle bugs. Always verify that lifetimes are correct, especially in multi-tenant and multi-threaded scenarios.


Practical Recommendation for Enterprise Teams in 2026

If you are building on .NET 8 or .NET 10, adopt Keyed Services as your default for static multi-implementation scenarios. Reduce factory boilerplate where the selection key is known at the injection point.

Keep the Factory Pattern for runtime, business-rule-driven selection. Treat it as the "strategy picker" layer โ€” it should evaluate context and return the right implementation, not just wrap a dictionary.

Retire custom Named Services registries on greenfield projects. If you are maintaining a legacy codebase on .NET 5 or 6 with this pattern, plan to migrate incrementally when you upgrade to .NET 8+.

For authoritative registration documentation, the Microsoft ASP.NET Core Dependency Injection docs and the Keyed Services documentation are the canonical references.


โ˜• Prefer a one-time tip? Buy us a coffee โ€” every bit helps keep the content coming!


Frequently Asked Questions

What are Keyed Services in ASP.NET Core? Keyed Services are a built-in dependency injection feature introduced in .NET 8 that lets you register multiple implementations of the same interface under different string or object keys, and then resolve them by key at injection points using the [FromKeyedServices] attribute or IServiceProvider.GetRequiredKeyedService<T>.

Can I use Keyed Services in .NET 6 or .NET 7? No. Keyed Services are only available in .NET 8 and later. For .NET 6/7 projects, the Factory Pattern or a custom Named Services registry is the standard approach for resolving multiple implementations of an interface.

When should I choose the Factory Pattern over Keyed Services in .NET 8? Use the Factory Pattern when the selection logic is dynamic and driven by runtime data โ€” such as user role, tenant configuration, database values, or feature flags. Keyed Services work best when the correct implementation is known statically at the injection point. For complex business-rule-driven selection, the factory provides a cleaner, more testable boundary.

Are Keyed Services compatible with Autofac or other third-party DI containers? Compatibility varies. The built-in Microsoft DI container in .NET 8 supports Keyed Services natively. Autofac has its own named/keyed registration mechanism that predates .NET 8, but it uses different syntax. If you replace the default container with Autofac or Lamar, the [FromKeyedServices] attribute may not work as expected without additional configuration.

What is the main downside of Keyed Services compared to Factory Pattern? The primary limitation is that keys are stringly-typed by default, which creates a risk of typos causing runtime failures rather than compile-time errors. You can mitigate this with constant fields or enums as keys. Additionally, Keyed Services are less natural for dynamic, runtime selection scenarios where the key itself must be computed before resolution.

Is the Named Services pattern still worth implementing in 2026? For new projects on .NET 8+, no. Keyed Services solve the same problem with far less boilerplate and better DI container integration. For existing codebases that already use a Named Services registry on .NET 5 or 6, it is worth maintaining until an .NET 8+ upgrade opens up a migration path to Keyed Services.

How do Keyed Services affect unit testing? Keyed Services are straightforward to mock in unit tests. Register a mock implementation under the same key in your test DI setup, or directly pass the mock to the constructor using [FromKeyedServices] in integration test scenarios. The testability profile is comparable to the Factory Pattern.

More from this blog

C

Coding Droplets

127 posts