AI Native  

Production Patterns for Microsoft.Extensions.AI Provider Switching

Introduction

Modern .NET applications rarely rely on a single AI provider. Organizations may use Azure OpenAI for enterprise compliance, OpenAI for advanced reasoning, local models for sensitive workloads, or other providers to optimize cost and availability. Hardcoding a single provider into your application makes it difficult to adapt when requirements change or a provider becomes unavailable.

The Microsoft.Extensions.AI library provides a common abstraction for integrating AI capabilities into .NET applications. By programming against shared interfaces instead of provider-specific SDKs, you can build applications that support provider switching, failover, testing, and future extensibility with minimal code changes.

In this article, you'll learn production-ready patterns for implementing provider switching using Microsoft.Extensions.AI, along with best practices, common pitfalls, and architectural guidance.

What Is Microsoft.Extensions.AI?

Microsoft.Extensions.AI is a .NET library that provides common abstractions for AI services. Rather than coupling your application to a specific vendor, you code against interfaces that multiple providers can implement.

Some benefits include:

  • Provider-independent application code

  • Easier testing with mock implementations

  • Simplified migration between AI providers

  • Centralized dependency injection

  • Cleaner architecture

Instead of referencing provider SDKs throughout your codebase, only the infrastructure layer needs provider-specific implementations.

Why Provider Switching Matters

Provider switching is valuable in several production scenarios:

ScenarioBenefit
Provider outageAutomatically fail over to another provider
Cost optimizationRoute requests to lower-cost models when appropriate
Compliance requirementsUse approved providers for regulated workloads
Performance optimizationChoose providers based on latency or workload
Development and testingReplace cloud models with local or mock implementations

Without abstraction, switching providers often requires changes throughout the application.

Architecture Overview

A typical architecture looks like this:

Application
      │
Business Services
      │
IChatClient
      │
Provider Router
      │
 ┌────────────┬───────────────┬─────────────┐
 │            │               │
Azure AI    OpenAI        Local Model

Business services communicate only with the abstraction, not directly with provider SDKs.

Registering AI Services

Using dependency injection keeps your application loosely coupled.

builder.Services.AddSingleton<IChatClient>(serviceProvider =>
{
    return CreateChatClient();
});

The registration method can determine which provider should be used based on configuration or runtime policies.

Using Configuration for Provider Selection

Store the active provider in configuration instead of hardcoding it.

appsettings.json

{
  "AI": {
    "Provider": "AzureOpenAI"
  }
}

Configuration-driven provider selection allows changes without recompiling the application.

Creating a Provider Factory

A factory centralizes provider creation.

public interface IChatClientFactory
{
    IChatClient Create(string providerName);
}

Implementation details remain isolated from the rest of the application.

Runtime Provider Switching

Applications may need different providers for different workloads.

For example:

Request TypePreferred Provider
Customer SupportAzure OpenAI
Internal AssistantLocal Model
Code GenerationOpenAI
Document AnalysisAzure OpenAI

Instead of a single provider, a routing service can evaluate request metadata and choose the appropriate implementation.

Implementing a Router

public interface IAiRouter
{
    IChatClient GetClient(string workload);
}

The router becomes responsible for selecting the correct provider.

Business services remain unaware of vendor-specific logic.

Implementing Failover

Production systems should anticipate temporary provider failures.

A common strategy is:

  1. Attempt the primary provider.

  2. Retry transient failures.

  3. Switch to a secondary provider if necessary.

  4. Log the failover event.

  5. Return the successful response.

Pseudo-code:

try
{
    return await primaryClient.CompleteAsync(prompt);
}
catch (TransientException)
{
    return await secondaryClient.CompleteAsync(prompt);
}

Only retry transient errors such as temporary network issues or rate limiting. Validation errors should generally not be retried.

Feature Flags for Provider Rollout

Feature flags allow gradual adoption of a new provider.

Example rollout:

  • 10% of requests → New provider

  • 50% of requests → New provider

  • 100% of requests → New provider

This approach reduces deployment risk and makes rollback straightforward if issues arise.

Dependency Injection Best Practices

Prefer constructor injection over creating providers directly.

public class ChatService
{
    private readonly IChatClient _client;

    public ChatService(IChatClient client)
    {
        _client = client;
    }
}

This keeps the service easy to test and avoids tight coupling.

Observability

Monitor provider behavior to support operational decisions.

Useful metrics include:

  • Request count

  • Average latency

  • Error rate

  • Token usage (if exposed by the provider)

  • Provider selection frequency

  • Failover events

These metrics can reveal when a provider is becoming unreliable or expensive.

Comparison of Provider Selection Strategies

StrategyAdvantagesDrawbacks
Configuration-basedSimple to implementRequires configuration change to switch
Feature flagsSafe incremental rolloutAdditional operational tooling
Rule-based routingOptimized per workloadMore complex routing logic
Health-based failoverImproves availabilityRequires health monitoring
Cost-aware routingOptimizes spendingNeeds accurate cost data

Common Mistakes

MistakeRecommended Approach
Using provider SDKs throughout the applicationDepend on shared abstractions
Hardcoding provider namesUse configuration or routing services
Ignoring transient failuresImplement retries and failover
Switching providers manuallyAutomate routing where appropriate
Mixing business logic with provider logicKeep provider logic in infrastructure

Troubleshooting

Provider Not Switching

Verify:

  • Configuration values

  • Dependency injection registration

  • Routing rules

Frequent Failovers

Check:

  • Provider health

  • Authentication credentials

  • Network connectivity

  • Rate-limit responses

Inconsistent Responses

Different providers may vary in output style, supported features, and response formats. Validate behavior before switching production traffic.

Best Practices

  • Code against Microsoft.Extensions.AI abstractions rather than provider SDKs.

  • Centralize provider selection in a router or factory.

  • Use dependency injection for all AI services.

  • Externalize provider configuration.

  • Monitor latency, failures, and usage.

  • Test failover paths regularly.

  • Keep provider-specific code isolated from business logic.

Conclusion

Microsoft.Extensions.AI helps .NET developers build applications that are flexible, maintainable, and resilient. By abstracting AI providers behind common interfaces, you can adopt new models, implement failover, optimize costs, and meet compliance requirements without rewriting core business logic.

A combination of dependency injection, provider factories, routing policies, configuration-based selection, and comprehensive observability creates a robust foundation for enterprise AI applications that can evolve alongside a rapidly changing AI ecosystem.

Frequently Asked Questions

Can I use multiple AI providers in the same application?

Yes. Microsoft.Extensions.AI is designed to support provider-independent abstractions, allowing different providers to be used for different workloads or as fallback options.

Does provider switching require application redeployment?

Not necessarily. If provider selection is driven by configuration or feature flags, changes can often be made without modifying application code.

Should every request use automatic failover?

Not always. Failover is most useful for transient infrastructure issues. For requests that depend on provider-specific capabilities or output consistency, explicit routing may be more appropriate.

Is Microsoft.Extensions.AI intended to replace provider SDKs?

No. It provides common abstractions that reduce coupling. Provider-specific SDKs are still used by the infrastructure layer where needed, while application code interacts with the shared interfaces.