Wenn du ein Payment-Backend, eine SaaS-Abrechnungs-Engine oder einen Enterprise-Pricing-Service auf dem Microsoft-Stack baust, ist die Integration einer Wechselkurs-API in C# / .NET eine der wirkungsvollsten Aufgaben, die du diese Woche ausliefern kannst. Modernes .NET liefert dir alles Nötige direkt mit — HttpClient, IHttpClientFactory, System.Text.Json, IMemoryCache und erstklassige Dependency Injection — und trotzdem zeigen die meisten Tutorials immer noch einen einzelnen WebClient.DownloadString-Schnipsel, der jedes produktive Code-Review nicht überstehen würde. Dieser Leitfaden führt dich von der ersten Anfrage bis zu einem typisierten, gecachten, resilienten Client, den du in ASP.NET Core, eine Blazor-App, ein WPF-Desktop-Tool oder eine Azure Function einbauen kannst.
Am Ende dieses Tutorials hast du einen wiederverwendbaren Finexly-Client für die Finexly-API-Dokumentation, einen kleinen Konsolen-Konverter und eine ASP.NET Core Minimal API, die Echtzeitkurse für den Rest deines Stacks bereitstellt — alles geschrieben in idiomatischem .NET 8 / .NET 9 nach den Mustern, die Microsoft selbst empfiehlt.
Warum C# / .NET eine starke Wahl für Währungsintegrationen ist
Währungsumrechnung in Produktion ist primär ein I/O-Problem: Kurse holen, cachen, ein paar Decimals multiplizieren, JSON zurückgeben. Die .NET-Runtime ist seit über einem Jahrzehnt genau für diese Last optimiert:
IHttpClientFactorylöst Socket-Exhaustion. Es pooledHttpMessageHandler-Instanzen, sodass duHttpClientüberall injizieren kannst, ohne Sockets zu lecken — die größte Quelle intermittierender API-Fehler, die wir in altem .NET-Code sehen.System.Text.Jsonist schnell und alloziert wenig. Es deserialisiert eine typische 170-Währungen-Antwort deutlich unter einer Millisekunde, ganz ohne Drittabhängigkeit.decimalist in die Sprache eingebaut. Du brauchst nie einen Dritt-BigDecimal, um mit Geld korrekt umzugehen — und das zählt, wenn du tausende umgerechnete Rechnungszeilen aufsummierst.- Polly ist die De-facto-Resilience-Bibliothek. Retries, Circuit Breaker und Timeouts sind einen
AddPolicyHandler-Call entfernt. - Minimal APIs und AOT. Einen winzigen Kurs-Microservice in 30 Zeilen aufzuziehen, ist heute realistisch und kaltstartet in Millisekunden.
Wenn du andere Stacks evaluierst, haben wir Parallelguides für Node.js, Python, Go und PHP. Dieser Artikel zielt speziell auf C# und setzt voraus, dass du mit async/await, Generic Hosting und Dependency Injection vertraut bist.
Voraussetzungen und Projekt-Setup
Du brauchst .NET 8 oder .NET 9. Prüfe deine SDK:
dotnet --versionLege eine neue Solution mit einer Class Library für den Client und einer Konsolen-App zum Ausprobieren an:
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.csprojDu brauchst außerdem einen Finexly-API-Key. Kostenlos registrieren — keine Kreditkarte, und der Free-Tier liefert 1.000 Requests pro Monat, mehr als genug für Entwicklung und CI.
Codiere den Key niemals fest ein. Speichere ihn in einer Umgebungsvariable:
# macOS / Linux
export FINEXLY_API_KEY="your_api_key_here"# Windows PowerShell
$env:FINEXLY_API_KEY = "your_api_key_here"In Produktion gehört der Key in Azure Key Vault, AWS Secrets Manager oder dotnet user-secrets für lokale Entwicklung.
Deine erste Anfrage mit HttpClient
Starten wir mit dem simpelsten lauffähigen Beispiel. Öffne Finexly.ConsoleApp/Program.cs und schreibe:
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"]);Ausführen:
dotnet run --project Finexly.ConsoleAppDu siehst ein Dictionary mit Währungscodes und ihren Kursen gegen USD. Funktioniert, aber hat alle Geruchsspuren eines 10-Zeilen-Beispiels: ungetypte object-Werte, kein Cache, keine Retries, manuell instanziierter HttpClient und den API-Key in der URL. Das richten wir alles.
Stark typisierte Response-Modelle definieren
System.Text.Json deserialisiert sauber in C#-record-Typen. Lege im Projekt Finexly.Client Models/LatestRatesResponse.cs an:
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
);Ein paar Details, die zählen:
decimalstattdouble. Gleitkomma-Fehler sind bei Geld inakzeptabel.decimalgibt dir 28–29 signifikante Stellen und exakte Dezimalarithmetik.IReadOnlyDictionary. Die Antwort ist nur lesbar — drücke das im Typ aus.DateOnly. Modernes .NET bringt einen echten Datumstyp mit. Nimm ihn stattDateTime, wenn keine Uhrzeit dazugehört.
Jetzt können wir deserialisieren, ohne dynamic- oder Dictionary<string, object>-Gymnastik.
Einen typisierten HttpClient mit IHttpClientFactory bauen
Das empfohlene .NET-Muster ist ein Typed Client, registriert über IHttpClientFactory. Er liefert Connection-Pooling, konfigurierbare Handler und einfache DI. Lege FinexlyClient.cs im Projekt Finexly.Client an:
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; }
}Ein paar Punkte:
- Der API-Key wandert in einen Header, nicht in die URL — sicherer für Logs, Proxies und Tracing.
- Ein
Timeoutvon 5 Sekunden verhindert, dass eine hängende Kurs-API einen langsamen Checkout verursacht. - Der Client akzeptiert ein
CancellationTokenauf jeder Methode — in modernem ASP.NET nicht verhandelbar. - Er verwendet
GetFromJsonAsync<T>, das GET, Statusprüfung und Deserialisierung in einem allokationsfreundlichen Call kombiniert.
Den Client in Dependency Injection registrieren
In deinem Program.cs (Console, ASP.NET Core, Worker — überall dieselbe API) verdrahtest du das so:
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>() registriert FinexlyClient als transienten Service und gibt ihm einen gepoolten HttpClient aus der Factory. Du kannst FinexlyClient jetzt in jeden Controller, jedes Minimal-API-Endpoint oder jeden Hintergrund-Service injizieren.
Retries und Circuit Breaker mit Polly hinzufügen
Externe APIs lassen gelegentlich eine Anfrage fallen, drosseln oder haben einen Netzwerk-Schluckauf. Polly ist die Standard-Resilience-Bibliothek von .NET und integriert sich in zwei Zeilen mit IHttpClientFactory.
Paket installieren:
dotnet add Finexly.ConsoleApp package Microsoft.Extensions.Http.PollyRetry mit Backoff und Circuit Breaker konfigurieren:
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)));Diese eine Registrierung retried bei 5xx, 408 und 429 mit exponentiellem Backoff (200 ms, 400 ms, 800 ms) und triggert einen Circuit Breaker, wenn die API komplett down ist — deine Checkout-Seite degradiert anständig, statt minutenlang Verbindungen offenzuhalten.
Antworten mit IMemoryCache cachen
Live-Wechselkurse ändern sich selten mehr als einmal pro Minute, also reduziert ein kleiner In-Process-Cache typischerweise die Upstream-Calls um 95 % oder mehr. .NET bringt IMemoryCache von Haus aus mit.
dotnet add Finexly.Client package Microsoft.Extensions.Caching.MemoryCachedFinexlyClient.cs anlegen:
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);
})!;
}
}Zusammen mit IMemoryCache registrieren:
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<CachedFinexlyClient>();Ein TTL von 60 Sekunden ist ein hervorragender Default für Checkout-Preise, Rechnungs-Previews und Admin-Dashboards. Für historische Daten kannst du den TTL auf Stunden oder Tage hochschrauben — historische Kurse ändern sich nicht.
Ein funktionierender Konsolen-Währungsrechner
Alles zusammengesetzt ergibt das eine kleine CLI, die currencyconverter --amount 100 --from USD --to JPY nachahmt:
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;Ein Single-File-Binary bauen, das du auf jeden Server kippen kannst:
dotnet publish Finexly.ConsoleApp -c Release -r linux-x64 \
--self-contained -p:PublishSingleFile=trueKurse mit einer ASP.NET Core Minimal API exponieren
Für die meisten Teams ist die richtige Architektur ein interner Kurs-Microservice, der die Upstream-API wrappt, cachet und allen anderen Services einen schnellen, kostenlosen, In-VPC-Endpoint gibt. ASP.NET Core Minimal APIs machen daraus eine 30-Zeilen-Datei:
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();Stelle das hinter dein normales API-Gateway, und jeder Service deiner Flotte kann GET /convert?from=USD&to=EUR&amount=99.95 mit unter 10 ms Latenz auf einen Cache-Hit aufrufen. Für eine Browser-Erfahrung sind dieselben Daten in unserem gehosteten Währungsrechner verfügbar.
Geld in C# korrekt behandeln
Ein paar defensive Muster, die sich beim ersten Audit eines Zahlungs-Hauptbuchs sofort rentieren:
- Immer
decimal, niemalsdoubleoderfloat.0.1 + 0.2ist in Gleitkomma bekanntlich nicht0.3— und dieser eine Rundungsfehler kann dich einen Chargeback-Streit kosten. - Bei der Anzeige runden, nicht bei der Berechnung. Volle Präzision durch jede Multiplikation halten und erst beim Anzeigen/Persistieren
Math.Round(value, 2, MidpointRounding.ToEven). - Rundungsmodus fixieren. Banker's Rounding (
ToEven) ist der Finanzindustrie-Standard in IEEE 754 und ISO 80000-1. - Währungen als opake Codes behandeln. Verwende ISO-4217-Strings — gehe nie davon aus, dass eine Währung zwei Dezimalstellen hat. JPY hat null, BHD hat drei. Unser ISO-4217-Leitfaden deckt die Randfälle ab.
Best Practices für die Produktion
Ein paar weitere Muster, die eine Hobby-Integration von einem System unterscheiden, das du jahrelang betreiben kannst:
- Setze knappe HTTP-Timeouts. Der Default-
HttpClient.Timeoutvon 100 Sekunden blockiert dir unter Last den Threadpool. Fünf Sekunden reichen für eine Kurs-API absolut. - Wickle jeden externen Call in ein
CancellationToken. ASP.NET Core gibt dir eines kostenlos; respektiere es, damit abgebrochene Requests nicht mit dem Upstream weiterreden. - Logge Statuscodes, nicht Bodies. Kurs-API-Antworten sind zu groß zum sinnvollen Loggen und enthalten Daten, die du nicht in Splunk haben willst.
- Überwache dein Kontingent. Vergleiche Preispläne und setze CloudWatch- / Application-Insights-Alerts auf 80 % deines Monatslimits.
- Pinne die Währungen, die du wirklich brauchst. Mit
symbols=EUR,GBP,JPYschrumpft die Antwort von ~10 KB auf unter 200 Bytes. - Cache aggressiv. Latenzkritische Checkouts kommen mit 30 Sekunden aus; Admin-Dashboards mit 5 Minuten.
Wenn du Anbieter evaluierst, vergleicht unser Vergleich kostenloser vs. kostenpflichtiger Kurs-APIs Genauigkeit, Uptime und Preise quer durch die wichtigsten Optionen.
Häufig gestellte Fragen
Welche Kurs-API funktioniert am besten mit C# und .NET?
Jede REST-API mit sauberem JSON und stabiler Uptime integriert sich gut über IHttpClientFactory. Finexly ist gezielt sprachagnostisch entworfen — dieselben Endpoints werden von Node-, Python-, Go-, Java- und .NET-Clients ohne SDK-Lock-in konsumiert. Siehe unsere Übersicht zur kostenlosen Kurs-API.
Newtonsoft.Json oder System.Text.Json?
Für neuen Code ab .NET 6 nutze System.Text.Json. Er ist 2–3× schneller, alloziert weniger und gehört zur BCL, also kein extra zu pflegendes Paket. Newtonsoft.Json ist weiterhin exzellent und im Legacy-Code wert, gepflegt zu werden, aber neue C#-Kursintegrationen sollten System.Text.Json als Default haben.
Brauche ich ein NuGet-SDK oder reicht HttpClient? Ein selbst geschriebener Typed Client (rund 50 Zeilen C#) ist fast immer die richtige Antwort. Er gibt dir volle Kontrolle über Caching, Retries, Telemetrie und Serialisierung und vermeidet das Versions-Drift-Risiko jedes Dritt-SDKs. Das Beispiel in diesem Tutorial ist produktionsreif.
Wie oft sollte ich Kurse in einer .NET-Anwendung refreshen? Für die meisten Use Cases — E-Commerce-Checkouts, SaaS-Billing, Rechnungsstellung — ist ein Cache von 30 bis 60 Sekunden sicher. Trading-Systeme brauchen offensichtlich Echtzeit, aber für alles andere ist Minuten-Frische für Nutzer unsichtbar und reduziert die API-Kosten drastisch.
Kann ich das in Azure Functions oder AWS Lambda laufen lassen?
Ja. IHttpClientFactory funktioniert in isolierten Azure Functions und in Lambda-Handlern mit Microsoft.Extensions.Hosting genau gleich. Für Kaltstart-sensible Workloads publish mit AOT (PublishAot=true), dann startet deine Funktion in unter 100 ms.
Ist decimal langsam gegenüber double?
decimal ist pro Rechenoperation rund 10–20× langsamer als double, aber eine Währungsumrechnung macht höchstens eine Handvoll Multiplikationen. Du wirst den Unterschied in einer realen Last nie messen — und der Korrektheitsgewinn ist bei Geld nicht verhandelbar.
Fazit
Du hast jetzt einen vollständigen, produktionsreifen Bauplan für die Integration einer Wechselkurs-API in C# / .NET: stark typisierte Modelle, einen von IHttpClientFactory gestützten Client, Polly-basierte Retries und Circuit Breaker, In-Process-Caching, einen Konsolen-Konverter und eine ASP.NET Core Minimal API. Jedes Muster hier stammt direkt aus den offiziellen Microsoft-Empfehlungen und ist in realem Fintech-Produktionscode erprobt.
Bereit, Echtzeitkurse in deine .NET-Anwendung zu bringen? Hol dir deinen kostenlosen Finexly-API-Key — keine Kreditkarte nötig. Starte mit 1.000 kostenlosen Requests pro Monat und upgrade, wenn dein Produkt wächst. Wenn du erst sehen willst, wie Finexly im Vergleich abschneidet, lies unseren Vergleich der Kurs-APIs.
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 →