Se stai costruendo un backend di pagamenti, un motore di billing SaaS o un servizio enterprise di pricing sullo stack Microsoft, integrare un'API di tassi di cambio in C# / .NET è uno dei task a maggior impatto che puoi rilasciare questa settimana. Il .NET moderno ti dà tutto pronto all'uso — HttpClient, IHttpClientFactory, System.Text.Json, IMemoryCache e dependency injection di prima classe — eppure la maggior parte dei tutorial mostra ancora un solo snippet WebClient.DownloadString che non passerebbe nessuna code review di produzione. Questa guida ti porta dalla prima richiesta fino a un client tipizzato, cacheato e resiliente che puoi piazzare in ASP.NET Core, in un'app Blazor, in un tool desktop WPF o in una Azure Function.
Alla fine di questo tutorial avrai un client Finexly riutilizzabile per la documentazione dell'API Finexly, un piccolo convertitore da console e un'API minimale ASP.NET Core che serve tassi in tempo reale al resto del tuo stack — il tutto scritto in .NET 8 / .NET 9 idiomatico, con i pattern raccomandati da Microsoft stessa.
Perché C# / .NET è una scelta forte per le integrazioni di valute
La conversione di valute in produzione è prevalentemente un problema di I/O: prendere i tassi, cacharli, moltiplicare qualche decimale, restituire JSON. Il runtime .NET è stato ottimizzato proprio per questo carico negli ultimi dieci anni:
IHttpClientFactoryrisolve l'esaurimento dei socket. Mette in pool le istanze diHttpMessageHandlercosicché tu possa iniettareHttpClientovunque senza leak di socket — la fonte numero uno di guasti intermittenti su API di cambio che vediamo in codice .NET legacy.System.Text.Jsonè veloce e alloca poco. Deserializza una tipica risposta di 170 valute molto sotto il millisecondo, senza dipendenze esterne.decimalè integrato nel linguaggio. Non hai mai bisogno di unBigDecimaldi terze parti per gestire correttamente il denaro — e questo conta quando sommi migliaia di righe di fatture convertite.- Polly è la libreria di resilienza de facto. Retry, circuit breaker e timeout sono a una chiamata di
AddPolicyHandler. - API minimali e AOT. Tirare su un microservizio di tassi minuscolo in 30 righe è ormai realistico, e parte a freddo in millisecondi.
Se stai valutando altri stack, abbiamo guide parallele per Node.js, Python, Go e PHP. Questo articolo è specifico per C# e assume che tu sia a tuo agio con async/await, generic hosting e dependency injection.
Prerequisiti e setup del progetto
Serve .NET 8 o .NET 9. Verifica l'SDK:
dotnet --versionCrea una solution con una class library per il client e un'app console per esercitarlo:
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.csprojServe anche un'API key Finexly. Registrati gratis — senza carta di credito, e il piano gratuito include 1.000 richieste al mese, più che sufficienti per sviluppo e CI.
Non hardcodare mai la chiave. Mettila in una variabile d'ambiente:
# macOS / Linux
export FINEXLY_API_KEY="your_api_key_here"# Windows PowerShell
$env:FINEXLY_API_KEY = "your_api_key_here"In produzione conservala in Azure Key Vault, AWS Secrets Manager o in dotnet user-secrets per lo sviluppo locale.
La tua prima richiesta con HttpClient
Partiamo dall'esempio più semplice che funziona. Apri 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"]);Eseguilo:
dotnet run --project Finexly.ConsoleAppVedrai un dizionario di codici valuta e i loro tassi rispetto al USD. Funziona, ma ha tutti i difetti di un esempio da 10 righe: valori object non tipati, niente cache, niente retry, un HttpClient istanziato a mano e l'API key nell'URL. Sistemiamo tutto.
Definire modelli di risposta fortemente tipizzati
System.Text.Json deserializza in modo pulito su record C#. Nel progetto Finexly.Client crea Models/LatestRatesResponse.cs:
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
);Dettagli da notare:
decimalinvece didouble. Gli errori di virgola mobile sono inaccettabili per il denaro.decimalti dà 28–29 cifre significative e aritmetica decimale esatta.IReadOnlyDictionary. La risposta è in sola lettura — esprimilo nel tipo.DateOnly. Il .NET moderno ha un vero tipo data. Usalo al posto diDateTimequando non c'è componente orario.
Ora possiamo deserializzare senza ginnastiche con dynamic o Dictionary<string, object>.
Costruire un HttpClient tipizzato con IHttpClientFactory
Il pattern raccomandato in .NET è un typed client registrato tramite IHttpClientFactory. Ti dà pooling delle connessioni, handler configurabili e DI semplice. Crea FinexlyClient.cs nel progetto 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; }
}Cose da sottolineare:
- L'API key sta in un header, non nell'URL — più sicuro per log, proxy e tracing.
- Un
Timeoutdi 5 secondi impedisce a un'API di cambio bloccata di trasformare il checkout in un checkout lento. - Il client prende un
CancellationTokenin ogni metodo — non negoziabile nel ASP.NET moderno. - Usa
GetFromJsonAsync<T>, che unisce GET, controllo di stato e deserializzazione in un'unica chiamata a basse allocazioni.
Registrare il client in Dependency Injection
Nel tuo Program.cs (Console, ASP.NET Core, Worker — stessa API ovunque) cabla così:
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>() registra FinexlyClient come servizio transient e gli fornisce un HttpClient pooled dalla factory. Ora puoi iniettare FinexlyClient in qualsiasi controller, endpoint di API minimale o servizio in background.
Aggiungere retry e circuit breaker con Polly
Le API esterne ogni tanto perdono una richiesta, fanno throttling o singhiozzano a livello di rete. Polly è la libreria di resilienza standard di .NET e si integra con IHttpClientFactory in due righe.
Installa il pacchetto:
dotnet add Finexly.ConsoleApp package Microsoft.Extensions.Http.PollyConfigura un retry con backoff e un 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)));Questa singola registrazione ritenta su 5xx, 408 e 429 con backoff esponenziale (200ms, 400ms, 800ms) e scatta un circuit breaker se l'API è completamente giù — la tua pagina di checkout degrada con grazia anziché tenere connessioni aperte per minuti.
Mettere in cache le risposte con IMemoryCache
I tassi di cambio in tempo reale raramente cambiano più di una volta al minuto, quindi una piccola cache in-process taglia tipicamente le chiamate upstream del 95% o più. IMemoryCache è già incluso in .NET.
dotnet add Finexly.Client package Microsoft.Extensions.Caching.MemoryCrea CachedFinexlyClient.cs:
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);
})!;
}
}Registralo insieme a IMemoryCache:
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<CachedFinexlyClient>();Un TTL di 60 secondi è un ottimo default per i prezzi di checkout, le preview di fatturazione e i dashboard admin. Per i dati storici alza il TTL a ore o giorni — i tassi storici non cambiano.
Un convertitore di valute da console funzionante
Mettendo tutto insieme, ecco una piccola CLI che imita currencyconverter --amount 100 --from USD --to JPY:
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;Costruisci un binario single-file che puoi mollare su qualsiasi server:
dotnet publish Finexly.ConsoleApp -c Release -r linux-x64 \
--self-contained -p:PublishSingleFile=trueEsporre i tassi con un'API minimale ASP.NET Core
Per la maggior parte dei team, l'architettura giusta è un microservizio interno di tassi che incapsula l'API upstream, aggiunge la cache e dà a tutti gli altri servizi un endpoint veloce, gratuito e in-VPC. Le API minimali di ASP.NET Core ne fanno un file da 30 righe:
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();Mettilo dietro al tuo solito API gateway e qualsiasi servizio della flotta può chiamare GET /convert?from=USD&to=EUR&amount=99.95 con latenza sotto i 10 millisecondi su cache hit. Per un'esperienza browser-side, gli stessi dati sono disponibili nel nostro convertitore di valute ospitato.
Trattare correttamente il denaro in C#
Pattern difensivi che si ripagano alla prima revisione di un libro mastro pagamenti:
- Usa sempre
decimal, maidoublenéfloat.0.1 + 0.2notoriamente non è0.3in virgola mobile — e quell'errore di arrotondamento può costarti una disputa chargeback. - Arrotonda in presentazione, non nei calcoli. Mantieni piena precisione attraverso ogni moltiplicazione e usa
Math.Round(value, 2, MidpointRounding.ToEven)solo al momento di mostrare o salvare. - Fissa la modalità di arrotondamento. L'arrotondamento bancario (
ToEven) è lo standard di settore in IEEE 754 e ISO 80000-1. - Tratta le valute come codici opachi. Usa stringhe ISO 4217 — non dare mai per scontato che una valuta abbia due decimali. JPY ne ha zero, BHD tre. La nostra guida ISO 4217 copre i casi limite.
Best practice per la produzione
Pattern aggiuntivi che separano un'integrazione hobbystica da un sistema che puoi gestire per anni:
- Imposta timeout HTTP stretti. Il
HttpClient.Timeoutdi default da 100 secondi blocca il thread pool sotto carico. Cinque secondi bastano e avanzano per un'API di cambio. - Avvolgi ogni chiamata esterna in un
CancellationToken. ASP.NET Core te ne passa uno gratis; rispettalo, così le richieste abortite non continuano a parlare con l'upstream. - Logga gli status code, non i body. Le risposte dell'API di cambio sono troppo grandi per essere loggate utilmente e possono contenere dati di tasso che non vuoi in Splunk.
- Monitora la quota. Confronta i piani e imposta alert in CloudWatch / Application Insights all'80% del tetto mensile.
- Pinna solo le valute che usi davvero. Mandare
symbols=EUR,GBP,JPYriduce la risposta da ~10 KB a meno di 200 byte. - Cacha in modo aggressivo. Checkout sensibili alla latenza vivono bene con cache da 30 secondi; i dashboard admin con 5 minuti.
Se stai valutando provider, il nostro confronto API di cambio gratuite vs a pagamento percorre precisione, uptime e prezzi delle principali opzioni.
Domande frequenti
Quale API di cambio funziona meglio con C# e .NET?
Qualsiasi REST API con JSON pulito e uptime stabile si integra bene via IHttpClientFactory. Finexly è disegnata per essere agnostica al linguaggio — gli stessi endpoint sono consumati da client Node, Python, Go, Java e .NET senza lock-in di SDK. Vedi la nostra panoramica dell'API di cambio gratuita.
Newtonsoft.Json o System.Text.Json?
Per codice nuovo su .NET 6+ usa System.Text.Json. È 2–3× più veloce, alloca meno e fa parte della BCL, quindi nessun pacchetto extra da mantenere. Newtonsoft.Json è ancora ottimo e va tenuto nel legacy, ma le nuove integrazioni di cambio in C# dovrebbero usare System.Text.Json come default.
Mi serve un SDK NuGet o basta HttpClient? Un typed client scritto a mano (circa 50 righe di C#) è quasi sempre la risposta giusta. Ti dà controllo totale su cache, retry, telemetria e serializzazione, ed evita il rischio di drift di versione di qualsiasi SDK di terze parti. L'esempio in questo tutorial è pronto per la produzione.
Ogni quanto vanno aggiornati i tassi in un'applicazione .NET? Per la maggior parte dei casi — checkout e-commerce, billing SaaS, fatturazione — una cache da 30 a 60 secondi è sicura. I sistemi di trading vogliono ovviamente dati in tempo reale, ma per tutto il resto la freschezza al minuto è invisibile agli utenti e riduce drasticamente la spesa API.
Lo posso eseguire in Azure Functions o AWS Lambda?
Sì. IHttpClientFactory funziona nello stesso modo in Azure Functions in processo isolato e in handler Lambda con Microsoft.Extensions.Hosting. Per workload sensibili al cold start, pubblica con AOT (PublishAot=true) e la funzione parte in meno di 100 ms.
decimal è lento rispetto a double?
decimal è circa 10–20× più lento di double per operazione aritmetica, ma una conversione di valuta fa al massimo qualche moltiplicazione. Non misurerai mai la differenza in un carico reale — e il guadagno in correttezza per il denaro non è negoziabile.
Conclusione
Hai ora un blueprint completo e a livello produzione per integrare un'API di tassi di cambio in C# / .NET: modelli fortemente tipizzati, client basato su IHttpClientFactory, retry e circuit breaker via Polly, cache in-process, convertitore da console e API minimale ASP.NET Core. Ogni pattern qui esce direttamente dalle linee guida ufficiali di Microsoft ed è battle-tested in codice fintech reale di produzione.
Pronto a portare tassi in tempo reale nella tua applicazione .NET? Ottieni la tua API key Finexly gratuita — senza carta di credito. Parti con 1.000 richieste gratuite al mese e fai upgrade quando il prodotto cresce. Se prima vuoi vedere come Finexly si confronta con altri provider, leggi il nostro confronto di API di cambio.
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 →