Als je een betalings-backend, een SaaS-billing-engine of een enterprise pricing-service op de Microsoft-stack bouwt, is het integreren van een wisselkoers-API in C# / .NET een van de meest impactvolle taken die je deze week kunt opleveren. Modern .NET geeft je alles kant-en-klaar — HttpClient, IHttpClientFactory, System.Text.Json, IMemoryCache en eersteklas dependency injection — en toch tonen de meeste tutorials nog steeds één WebClient.DownloadString-snippet die geen enkele productie-code-review zou doorstaan. Deze gids voert je van je eerste request tot een typed, gecachte, veerkrachtige client die je in ASP.NET Core, een Blazor-app, een WPF-desktoptool of een Azure Function kunt droppen.
Aan het eind van deze tutorial heb je een herbruikbare Finexly-client voor de Finexly API-documentatie, een kleine console-converter en een ASP.NET Core Minimal API die real-time koersen aan de rest van je stack levert — alles geschreven in idiomatisch .NET 8 / .NET 9 volgens de patronen die Microsoft zelf aanbeveelt.
Waarom C# / .NET een sterke keuze is voor valuta-integraties
Valutaomrekening in productie is vooral een I/O-probleem: koersen ophalen, cachen, wat decimalen vermenigvuldigen, JSON teruggeven. De .NET-runtime is het laatste decennium juist voor die belasting geoptimaliseerd:
IHttpClientFactorylost socket-uitputting op. Het pooledHttpMessageHandler-instanties, zodat jeHttpClientoveral kunt injecteren zonder sockets te lekken — de grootste bron van intermitterende API-storingen die we in oude .NET-code zien.System.Text.Jsonis snel en alloceert weinig. Het deserialiseert een typische 170-valutarespons ruim binnen één milliseconde, zonder externe afhankelijkheid.decimalis ingebouwd in de taal. Je hebt nooit een externeBigDecimalnodig om geld correct te verwerken — en dat telt als je duizenden omgerekende factuurregels optelt.- Polly is de de-facto resilience-bibliotheek. Retries, circuit breakers en timeouts zijn één
AddPolicyHandler-aanroep verwijderd. - Minimal API's en AOT. Een piepkleine koersen-microservice in 30 regels opzetten is nu realistisch en koudstart in milliseconden.
Evalueer je andere stacks? We hebben parallelle gidsen voor Node.js, Python, Go en PHP. Dit artikel is specifiek voor C# en gaat ervan uit dat je vertrouwd bent met async/await, Generic Hosting en dependency injection.
Vereisten en projectopzet
Je hebt .NET 8 of .NET 9 nodig. Controleer je SDK:
dotnet --versionMaak een nieuwe solution met een class library voor de client en een console-app om die te testen:
mkdir Finexly.Sample && cd Finexly.Sample
dotnet new sln -n Finexly.Sample
dotnet new classlib -n Finexly.Client
dotnet new console -n Finexly.ConsoleApp
dotnet sln add Finexly.Client/Finexly.Client.csproj
dotnet sln add Finexly.ConsoleApp/Finexly.ConsoleApp.csproj
dotnet add Finexly.ConsoleApp/Finexly.ConsoleApp.csproj reference Finexly.Client/Finexly.Client.csprojJe hebt ook een Finexly API-key nodig. Registreer gratis — geen creditcard, en het gratis plan biedt 1.000 requests per maand, ruim voldoende voor development en CI.
Hardcode de key nooit. Zet hem in een environment variable:
# macOS / Linux
export FINEXLY_API_KEY="your_api_key_here"# Windows PowerShell
$env:FINEXLY_API_KEY = "your_api_key_here"In productie hoort de key in Azure Key Vault, AWS Secrets Manager of dotnet user-secrets voor lokale ontwikkeling.
Je eerste request met HttpClient
We beginnen met het simpelste werkende voorbeeld. Open Finexly.ConsoleApp/Program.cs:
using System.Net.Http;
using System.Net.Http.Json;
var apiKey = Environment.GetEnvironmentVariable("FINEXLY_API_KEY")
?? throw new InvalidOperationException("FINEXLY_API_KEY not set");
using var http = new HttpClient
{
BaseAddress = new Uri("https://api.finexly.com/v1/")
};
var url = $"latest?base=USD&apikey={apiKey}";
var payload = await http.GetFromJsonAsync<Dictionary<string, object>>(url);
Console.WriteLine(payload?["rates"]);Voer uit:
dotnet run --project Finexly.ConsoleAppJe ziet een dictionary met valutacodes en hun koersen ten opzichte van USD. Dit werkt, maar bevat alle smells die je van een 10-regelig voorbeeld verwacht: ongetypeerde object-waarden, geen cache, geen retries, een handmatig geïnstantieerde HttpClient en de API-key in de URL. Dat lossen we allemaal op.
Sterk getypeerde responsmodellen definiëren
System.Text.Json deserialiseert netjes naar C#-record-types. Maak in het project Finexly.Client Models/LatestRatesResponse.cs aan:
using System.Text.Json.Serialization;
namespace Finexly.Client.Models;
public sealed record LatestRatesResponse(
[property: JsonPropertyName("base")] string Base,
[property: JsonPropertyName("date")] DateOnly Date,
[property: JsonPropertyName("rates")] IReadOnlyDictionary<string, decimal> Rates
);Een paar details om op te merken:
decimalin plaats vandouble. Floating-point-fouten zijn voor geld onacceptabel.decimalgeeft je 28–29 significante cijfers en exacte 10-tallige rekenkunde.IReadOnlyDictionary. De respons is read-only — druk dat uit in het type.DateOnly. Modern .NET levert een echt datumtype. Gebruik het in plaats vanDateTimeals er geen tijdcomponent is.
Nu kunnen we deserialiseren zonder dynamic- of Dictionary<string, object>-gymnastiek.
Een typed HttpClient bouwen met IHttpClientFactory
Het door .NET aanbevolen patroon is een typed client, geregistreerd via IHttpClientFactory. Dat levert connectie-pooling, configureerbare handlers en eenvoudige DI. Maak FinexlyClient.cs aan in het project Finexly.Client:
using System.Net.Http.Json;
using Finexly.Client.Models;
namespace Finexly.Client;
public sealed class FinexlyClient
{
private readonly HttpClient _http;
private readonly string _apiKey;
public FinexlyClient(HttpClient http, FinexlyOptions options)
{
_http = http;
_apiKey = options.ApiKey;
_http.BaseAddress = new Uri("https://api.finexly.com/v1/");
_http.DefaultRequestHeaders.Add("X-Api-Key", _apiKey);
_http.Timeout = TimeSpan.FromSeconds(5);
}
public async Task<LatestRatesResponse> GetLatestRatesAsync(
string baseCurrency = "USD",
IEnumerable<string>? symbols = null,
CancellationToken cancellationToken = default)
{
var query = $"latest?base={baseCurrency}";
if (symbols is not null)
{
query += $"&symbols={string.Join(",", symbols)}";
}
var response = await _http.GetFromJsonAsync<LatestRatesResponse>(
query, cancellationToken);
return response
?? throw new InvalidOperationException("Empty response from Finexly");
}
public async Task<decimal> ConvertAsync(
string from, string to, decimal amount,
CancellationToken cancellationToken = default)
{
var rates = await GetLatestRatesAsync(from, new[] { to }, cancellationToken);
if (!rates.Rates.TryGetValue(to, out var rate))
{
throw new ArgumentException($"Unknown currency: {to}");
}
return amount * rate;
}
}
public sealed class FinexlyOptions
{
public required string ApiKey { get; init; }
}Een paar punten om te benadrukken:
- De API-key zit in een header, niet in de URL — veiliger voor logs, proxies en tracing.
- Een
Timeoutvan 5 seconden voorkomt dat een vastgelopen koers-API een traag afrekenproces wordt. - De client accepteert in elke methode een
CancellationToken— in modern ASP.NET niet onderhandelbaar. - Hij gebruikt
GetFromJsonAsync<T>, dat GET, statuscontrole en deserialisatie combineert in één allocatie-vriendelijke aanroep.
De client registreren in dependency injection
In je Program.cs (Console, ASP.NET Core, Worker — overal dezelfde API) zet je het zo aan:
using Finexly.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton(new FinexlyOptions
{
ApiKey = Environment.GetEnvironmentVariable("FINEXLY_API_KEY")!
});
builder.Services.AddHttpClient<FinexlyClient>();
using var host = builder.Build();
var finexly = host.Services.GetRequiredService<FinexlyClient>();
var eur = await finexly.ConvertAsync("USD", "EUR", 100m);
Console.WriteLine($"100 USD = {eur:0.00} EUR");AddHttpClient<FinexlyClient>() registreert FinexlyClient als transient en geeft hem een gepoolde HttpClient uit de factory. Je kunt FinexlyClient nu in elke controller, Minimal API-endpoint of achtergrond-service injecteren.
Retries en een circuit breaker toevoegen met Polly
Externe API's laten af en toe een request vallen, throttlen, of hikken op netwerkniveau. Polly is de standaard resilience-bibliotheek van .NET en integreert in twee regels met IHttpClientFactory.
Installeer het pakket:
dotnet add Finexly.ConsoleApp package Microsoft.Extensions.Http.PollyConfigureer een retry met backoff en een circuit breaker:
using Polly;
using Polly.Extensions.Http;
builder.Services.AddHttpClient<FinexlyClient>()
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(r => (int)r.StatusCode == 429)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt =>
TimeSpan.FromMilliseconds(200 * Math.Pow(2, attempt))))
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30)));Deze ene registratie retry't op 5xx, 408 en 429 met exponential backoff (200ms, 400ms, 800ms) en triggert een circuit breaker als de API volledig down is — je checkout-pagina degradeert netjes in plaats van connecties minutenlang open te houden.
Antwoorden cachen met IMemoryCache
Live wisselkoersen veranderen zelden vaker dan eenmaal per minuut, dus een kleine in-process cache vermindert upstream-calls doorgaans met 95% of meer. IMemoryCache zit standaard in .NET.
dotnet add Finexly.Client package Microsoft.Extensions.Caching.MemoryMaak CachedFinexlyClient.cs aan:
using Finexly.Client.Models;
using Microsoft.Extensions.Caching.Memory;
namespace Finexly.Client;
public sealed class CachedFinexlyClient
{
private readonly FinexlyClient _inner;
private readonly IMemoryCache _cache;
private static readonly TimeSpan DefaultTtl = TimeSpan.FromSeconds(60);
public CachedFinexlyClient(FinexlyClient inner, IMemoryCache cache)
{
_inner = inner;
_cache = cache;
}
public Task<LatestRatesResponse> GetLatestRatesAsync(
string baseCurrency,
CancellationToken cancellationToken = default)
{
var key = $"finexly:latest:{baseCurrency}";
return _cache.GetOrCreateAsync(key, entry =>
{
entry.AbsoluteExpirationRelativeToNow = DefaultTtl;
return _inner.GetLatestRatesAsync(baseCurrency,
cancellationToken: cancellationToken);
})!;
}
}Registreer hem naast IMemoryCache:
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<CachedFinexlyClient>();Een TTL van 60 seconden is een uitstekende default voor checkout-prijzen, billing-previews en admin-dashboards. Voor historische data zet je de TTL op uren of dagen — historische koersen veranderen niet.
Een werkende console-valuta-converter
Alles bij elkaar levert dat een kleine CLI op die currencyconverter --amount 100 --from USD --to JPY imiteert:
using Finexly.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddSingleton(new FinexlyOptions
{
ApiKey = Environment.GetEnvironmentVariable("FINEXLY_API_KEY")!
});
builder.Services.AddHttpClient<FinexlyClient>();
builder.Services.AddSingleton<CachedFinexlyClient>();
using var host = builder.Build();
decimal amount = 1m;
string from = "USD", to = "EUR";
for (int i = 0; i < args.Length - 1; i++)
{
switch (args[i])
{
case "--amount": amount = decimal.Parse(args[i + 1]); break;
case "--from": from = args[i + 1].ToUpperInvariant(); break;
case "--to": to = args[i + 1].ToUpperInvariant(); break;
}
}
var finexly = host.Services.GetRequiredService<CachedFinexlyClient>();
var rates = await finexly.GetLatestRatesAsync(from);
if (!rates.Rates.TryGetValue(to, out var rate))
{
Console.Error.WriteLine($"Unknown currency: {to}");
return 1;
}
Console.WriteLine($"{amount:0.00} {from} = {amount * rate:0.00} {to}");
return 0;Bouw een single-file binary die je op elke server kunt droppen:
dotnet publish Finexly.ConsoleApp -c Release -r linux-x64 \
--self-contained -p:PublishSingleFile=trueKoersen aanbieden met een ASP.NET Core Minimal API
Voor de meeste teams is de juiste architectuur een interne koersen-microservice die de upstream-API wrappt, caching toevoegt en alle andere services een snelle, gratis, in-VPC endpoint geeft. ASP.NET Core Minimal API's maken daar een bestand van 30 regels van:
using Finexly.Client;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddSingleton(new FinexlyOptions
{
ApiKey = builder.Configuration["Finexly:ApiKey"]!
});
builder.Services.AddHttpClient<FinexlyClient>();
builder.Services.AddSingleton<CachedFinexlyClient>();
var app = builder.Build();
app.MapGet("/rates", async (
string? @base,
CachedFinexlyClient finexly,
CancellationToken ct) =>
{
var rates = await finexly.GetLatestRatesAsync(@base ?? "USD", ct);
return Results.Ok(rates);
});
app.MapGet("/convert", async (
string from, string to, decimal amount,
CachedFinexlyClient finexly,
CancellationToken ct) =>
{
var rates = await finexly.GetLatestRatesAsync(from, ct);
return rates.Rates.TryGetValue(to, out var rate)
? Results.Ok(new { from, to, amount, converted = amount * rate })
: Results.NotFound(new { error = $"Unknown currency {to}" });
});
app.Run();Deploy dit achter je gebruikelijke API-gateway en elke service in je vloot kan GET /convert?from=USD&to=EUR&amount=99.95 aanroepen met sub-10-milliseconden latency bij een cache hit. Voor een browser-ervaring zijn dezelfde gegevens beschikbaar in onze gehoste valuta-converter.
Geld correct verwerken in C#
Een paar defensieve patronen die zichzelf terugbetalen bij de eerste audit van een betalings-grootboek:
- Gebruik altijd
decimal, nooitdoubleoffloat.0.1 + 0.2is in floating-point bekend niet gelijk aan0.3— en die ene afrondingsfout kan je een chargeback-geschil kosten. - Rond af bij weergave, niet bij berekening. Houd volledige precisie door elke vermenigvuldiging en gebruik
Math.Round(value, 2, MidpointRounding.ToEven)pas bij tonen of opslaan. - Fixeer de afrondingsmodus. Banker's rounding (
ToEven) is in IEEE 754 en ISO 80000-1 de standaard van de financiële sector. - Behandel valuta's als ondoorzichtige codes. Gebruik ISO 4217-strings — ga er nooit van uit dat een valuta twee decimalen heeft. JPY heeft er nul, BHD drie. Onze ISO 4217-gids behandelt de randgevallen.
Best practices voor productie
Een paar extra patronen die een hobby-integratie scheiden van een systeem dat je jaren kunt draaien:
- Stel strakke HTTP-timeouts in. De default
HttpClient.Timeoutvan 100 seconden blokkeert je threadpool onder belasting. Vijf seconden is ruim genoeg voor een koers-API. - Verpak elke externe call in een
CancellationToken. ASP.NET Core geeft je er gratis een — respecteer hem, zodat afgebroken requests niet met de upstream blijven praten. - Log statuscodes, geen bodies. Antwoorden van koers-API's zijn te groot om nuttig te loggen en kunnen koersgegevens bevatten die je niet in Splunk wilt.
- Monitor je quotum. Vergelijk prijsplannen en zet CloudWatch- / Application Insights-alerts op 80% van je maandlimiet.
- Pin alleen de valuta's die je echt gebruikt.
symbols=EUR,GBP,JPYmeesturen krimpt de respons van ~10 KB naar onder de 200 bytes. - Cache agressief. Latency-gevoelige checkouts kunnen prima met een cache van 30 seconden; admin-dashboards met 5 minuten.
Evalueer je providers? Onze vergelijking gratis vs. betaalde koers-API's loopt door precisie, uptime en prijzen van de belangrijkste opties.
Veelgestelde vragen
Welke koers-API werkt het best met C# en .NET?
Elke REST API met schone JSON en stabiele uptime integreert prima via IHttpClientFactory. Finexly is bewust taal-agnostisch ontworpen — dezelfde endpoints worden door Node-, Python-, Go-, Java- en .NET-clients gebruikt, zonder SDK-lock-in. Zie ons overzicht van de gratis koers-API.
Newtonsoft.Json of System.Text.Json?
Gebruik voor nieuwe code op .NET 6+ System.Text.Json. Hij is 2–3× sneller, alloceert minder en zit in de BCL, dus geen extra te onderhouden pakket. Newtonsoft.Json blijft uitstekend en is in legacy waardevol, maar nieuwe C#-koersintegraties zouden standaard System.Text.Json moeten gebruiken.
Heb ik een NuGet-SDK nodig of volstaat HttpClient? Een zelfgeschreven typed client (zo'n 50 regels C#) is bijna altijd het juiste antwoord. Het geeft je volledige controle over caching, retries, telemetrie en serialisatie en vermijdt het versiedrift-risico van elke externe SDK. Het voorbeeld in deze tutorial is productieklaar.
Hoe vaak moet ik koersen verversen in een .NET-applicatie? Voor de meeste use cases — e-commerce-checkouts, SaaS-billing, facturatie — is een cache van 30 tot 60 seconden veilig. Tradingsystemen hebben uiteraard real-time nodig, maar voor al het andere is versheid op minuutniveau onzichtbaar voor gebruikers en verlaagt het de API-kosten drastisch.
Kan ik dit draaien in Azure Functions of AWS Lambda?
Ja. IHttpClientFactory werkt op dezelfde manier in isolated-process Azure Functions en in Lambda-handlers met Microsoft.Extensions.Hosting. Voor cold-start-gevoelige workloads publish je met AOT (PublishAot=true) en start je functie in minder dan 100 ms.
Is decimal traag vergeleken met double?
decimal is per rekenkundige operatie ongeveer 10–20× trager dan double, maar een valutaomrekening doet hooguit een handvol vermenigvuldigingen. In een echte workload meet je het verschil nooit — en de correctheidswinst is voor geld niet onderhandelbaar.
Tot slot
Je hebt nu een complete, productiewaardige blauwdruk voor het integreren van een wisselkoers-API in C# / .NET: sterk getypeerde modellen, een door IHttpClientFactory gevoede client, Polly-retries en circuit breaker, in-process caching, een console-converter en een ASP.NET Core Minimal API. Elk patroon hier komt direct uit Microsofts officiële richtlijnen en is beproefd in echte fintech-productiecode.
Klaar om real-time koersen in je .NET-applicatie te brengen? Haal je gratis Finexly API-key op — geen creditcard nodig. Begin met 1.000 gratis requests per maand en upgrade naarmate je product groeit. Wil je eerst zien hoe Finexly zich verhoudt tot andere aanbieders, lees dan onze vergelijking van koers-API's.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →