Adapter Pattern in .NET – Decouple External Libraries and Wrap APIs for Easier Testing
Introduction
Many of us have been there: we start using an external API or library, and over time it becomes deeply integrated across our codebase. Then one day, that library gets deprecated, becomes paid, or introduces breaking changes in a new version.
Suddenly, you have to update dozens of files, fix compilation errors, and run all your tests again just because the external dependency changed.
Sometimes this even happens with popular libraries, like fluent APIs or graphic libraries, where a simple update breaks existing behavior. In an automated test, you might catch the issue early, but fixing it everywhere still takes time and effort.
That’s exactly where the Adapter Pattern comes in. It helps you isolate your code from external dependencies, so when a library changes (or you decide to replace it), your main application remains stable and easy to maintain.
What Is the Adapter Pattern?
The Adapter Pattern is one of the most well-known structural design patterns from the Gang of Four (GoF) book “Design Patterns: Elements of Reusable Object-Oriented Software”, published in 1994.
Its main goal is to make two incompatible interfaces work together just like a power adapter that allows a European plug to fit into a U.S. socket.
In software terms, the Adapter Pattern acts as a translator between your code and an external component (a class, library, or API) that has a different interface.
Instead of calling that external library directly, you define your own interface that represents what your application actually needs, and then implement an adapter that converts your interface calls into the specific calls required by the external dependency.
This pattern became popular in the early days of object-oriented programming because it allowed developers to reuse existing code without having to modify it.
Today, it’s still extremely relevant in modern .NET development especially when integrating third-party SDKs, legacy systems, or cloud APIs because it helps keep your application architecture clean, testable, and independent from external libraries.
How the Adapter Pattern Works
At its core, the Adapter Pattern sits between your application code and an external dependency, acting as a bridge between them.
It hides the details of the external API behind a consistent interface that fits your application’s domain.
You can think of it as a small layer that translates method calls your app talks to the adapter, and the adapter talks to the external service or library.
Let’s visualize this with a simple diagram:
┌───────────────────────────────┐
│ Your Application │
│ (Controllers, Services, UI) │
└──────────────┬────────────────┘
│
▼
┌──────────────┐
│ Adapter │ ← Converts your interface calls
└──────────────┘
│
▼
┌───────────────────────────────┐
│ External Library or API │
│ (3rd Party SDK, HTTP Client) │
└───────────────────────────────┘
In .NET, this is usually implemented through an interface and one or more adapter classes:
- You define an interface that represents what your application needs (e.g., IWeatherService or IEmailService).
- You implement an adapter that wraps the actual library or API calls.
- The rest of your application depends only on the interface not on the library itself.
Structure of the Adapter Pattern
Understanding the structure of the Adapter Pattern helps you recognize where each piece fits in your application design.
Participants
- Target – The interface your application depends on.
It defines the methods the rest of your code will call (e.g., IEmailService, IWeatherService). - Adapter – A class that implements the Target interface and translates those calls into the format expected by the external dependency.
It “adapts” your interface to the external one. - Adaptee – The existing class, library, or API you want to integrate.
It provides useful functionality, but its interface doesn’t match what your application expects. - Client – The part of your application that uses the Target interface.
It knows nothing about the external dependency; it just calls the Target.

When to Use the Adapter Pattern
You should consider using the Adapter Pattern when:
- Integrating Legacy Code
You need to connect an old system or library with your modern application that expects a different interface. - Unifying Different Interfaces
Multiple classes or APIs offer similar functionality but with incompatible method signatures. - Improving Code Reusability
You want to reuse an existing library or class without changing its internal code. - Switching or Abstracting External Libraries
You rely on external SDKs or APIs (for example, payment gateways, storage providers, or AI models) and want the freedom to replace them later without refactoring the whole application.
Common Real-World Examples
- Payment Gateways: Wrapping Stripe, PayPal, or Braintree SDKs behind a unified interface.
- Cloud Providers: Using adapters to switch between Azure Blob Storage, AWS S3, and Google Cloud Storage.
- Media Players: Converting different media formats into a common player interface.
- Data Converters: Translating data formats (JSON, XML, CSV) for interoperability.
Classic vs Modern Implementation of the Adapter Pattern
If you search online for “Adapter Pattern in C#”, you’ll probably find examples like this one from the classic Gang of Four (GoF) book or from sites such as Refactoring Guru and Dofactory:
public class Target
{
public virtual void Request()
{
Console.WriteLine("Called Target Request()");
}
}
public class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Called SpecificRequest()");
}
}
public class Adapter : Target
{
private Adaptee adaptee = new Adaptee();
public override void Request()
{
adaptee.SpecificRequest();
}
}
public class Program
{
public static void Main(string[] args)
{
Target target = new Adapter();
target.Request();
}
}
This version perfectly illustrates the concept:
the Adapter inherits from Target and internally calls the Adaptee.
It works well as a structural example, but in modern .NET development we usually need something more flexible, especially when working with external APIs, SDKs, or libraries.
From Inheritance to Composition
In modern architecture, instead of making the adapter inherit from a base class, we make it implement an interface.
This approach is called composition over inheritance and is widely used in frameworks like ASP.NET Core.
Why This Matters
Modern .NET applications rely heavily on dependency injection, unit testing, and SOLID principles.
Using an interface-based adapter gives you:
- Flexibility: Replace or update external libraries without refactoring the core code.
- Testability: Swap the real adapter with a fake or mock version in tests.
- Loose coupling: The application depends only on abstractions, not concrete implementations.
In short, the modern adapter pattern follows the same idea as the original,
but it evolves it for today’s real-world requirements where clean architecture, testability, and maintainability are essential.
Here’s the modern equivalent:
Practical Example: Wrapping a Public Weather API
Disclaimer: This example is purely for educational purposes. There are better ways to write code and applications that can optimize this example. Use this as a starting point for learning, but always strive to follow best practices and improve your implementation.
In this example, we’ll use the Adapter Pattern to wrap the Open-Meteo API, a free public service that provides real-time weather data without authentication.
We’ll first define a clean interface (IWeatherService) that our application depends on, then create an adapter (OpenMeteoAdapter) that handles the actual HTTP requests.
Finally, we’ll expose it through a simple Minimal API that anyone can run locally.
Prerequisites
Before starting, make sure you have the following installed:
- .NET SDK: Download and install the .NET SDK if you haven’t already.
- Visual Studio Code (VSCode): Install Visual Studio Code for a lightweight code editor.
- C# Extension for VSCode: Install the C# extension for VSCode to enable C# support.
Step 1 – Create a Minimal API Project
Open your terminal and create a new project using the .NET CLI:
dotnet new web -n AdapterPatternDemoApi
cd AdapterPatternDemoApi
This command creates a simple Minimal API project using the ASP.NET Core template.
Step 2 – Open the Project in VS Code
In the same terminal, type:
code .
This opens the project folder in Visual Studio Code.
Step 3 – Create the Interface
Let’s start by defining a simple contract that represents what our application needs:
public interface IWeatherService
{
Task<double> GetTemperatureAsync(double latitude, double longitude);
}
This interface defines a single method to retrieve the current temperature based on coordinates.
Step 4 – Implement the Adapter
Now we create an adapter that talks to the Open-Meteo REST API using HttpClient.
using System.Net.Http.Json;
public class OpenMeteoAdapter : IWeatherService
{
private readonly HttpClient _client;
public OpenMeteoAdapter(HttpClient client)
{
_client = client;
}
public async Task<double> GetTemperatureAsync(double latitude, double longitude)
{
var url = string.Format(
CultureInfo.InvariantCulture,
"https://api.open-meteo.com/v1/forecast?latitude={0}&longitude={1}¤t=temperature_2m",
latitude, longitude);
var response = await _client.GetFromJsonAsync<OpenMeteoResponse>(url);
return response?.Current?.Temperature_2m ?? double.NaN;
}
}
public class OpenMeteoResponse
{
public CurrentWeather? Current { get; set; }
}
public class CurrentWeather
{
public double Temperature_2m { get; set; }
}

Here, the adapter translates our application’s request (GetTemperatureAsync) into the specific format required by the Open-Meteo API.
Step 5 – Create a Minimal API
Now we build a small Minimal API that consumes our adapter.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<IWeatherService, OpenMeteoAdapter>();
var app = builder.Build();
app.MapGet("/weather", async (double lat, double lon, IWeatherService weatherService) =>
{
var temp = await weatherService.GetTemperatureAsync(lat, lon);
return Results.Ok(new
{
latitude = lat,
longitude = lon,
temperature = temp
});
});
app.Run();

Step 6 – Run the Project
Open your terminal in the project directory and run:
dotnet run
Then visit this URL in your browser:
http://localhost:5000/weather?lat=48.1374&lon=11.5755
You should see a JSON response like:
{
"latitude": 48.1374,
"longitude": 11.5755,
"temperature": 9.6
}

Step 7 – Add a Fake Adapter for Testing
Finally, we can easily mock or fake the service in unit tests:
// FakeWeatherService.cs
public class FakeWeatherService : IWeatherService
{
public Task<double> GetTemperatureAsync(double latitude, double longitude)
=> Task.FromResult(25.0);
}
With this fake adapter, your tests don’t need to hit the real API they can run instantly and predictably.
You’ve just implemented the Adapter Pattern to wrap a real external API in a clean, testable, and flexible way.
If the API changes tomorrow or you switch to another provider (like WeatherAPI or OpenWeatherMap), you only need to replace the adapter the rest of your app remains untouched.
Conclusion
The Adapter Pattern is a simple yet powerful design pattern that can make a big difference in the long-term maintainability of your applications.
By taking the time to design a small abstraction layer between your code and external dependencies, you protect your system from sudden breaking changes, API deprecations, and vendor lock-in.
A few extra minutes of thoughtful design can save hours or even days of refactoring later.
More importantly, this approach reduces technical debt you gain cleaner, more testable, and future-proof code.
In short, by combining a simple pattern with careful analysis and design, you can build software that is easier to evolve, safer to update, and faster to maintain.
If you think your friends or network would find this article useful, please consider sharing it with them. Your support is greatly appreciated.
Thanks for reading!
Discover CodeSwissKnife Bar, your all-in-one, offline Developer Tools from Your Menu Bar
