Se você está construindo um backend de pagamentos, um motor de faturação SaaS ou um serviço empresarial de preços sobre o stack da Microsoft, integrar uma API de taxas de câmbio em C# / .NET é uma das tarefas de maior impacto que pode entregar esta semana. O .NET moderno já vem com tudo o que precisa de fábrica — HttpClient, IHttpClientFactory, System.Text.Json, IMemoryCache e injeção de dependência de primeira classe — e ainda assim a maioria dos tutoriais mostra apenas um trecho WebClient.DownloadString que jamais passaria em uma revisão de código de produção. Este guia leva você da sua primeira requisição até um cliente tipado, com cache e resiliente, que pode ser usado em ASP.NET Core, num app Blazor, numa ferramenta desktop WPF ou numa Azure Function.
Ao final deste tutorial você terá um cliente Finexly reutilizável para a documentação da API Finexly, um pequeno conversor de console e uma API mínima ASP.NET Core que serve taxas de câmbio em tempo real para o resto do seu stack — tudo escrito em .NET 8 / .NET 9 idiomático com os padrões que a própria Microsoft recomenda.
Por que C# / .NET é uma escolha forte para integrações de moedas
A conversão de moedas em produção é principalmente um problema de I/O: buscar taxas, cachear, multiplicar uns decimais e retornar JSON. O runtime do .NET foi otimizado para exatamente essa carga ao longo da última década:
IHttpClientFactoryresolve a exaustão de sockets. Ele faz pooling de instânciasHttpMessageHandlerpara que você possa injetarHttpClientem todos os lugares sem vazar sockets — a maior fonte de falhas intermitentes em APIs de câmbio que vemos em código .NET legado.System.Text.Jsoné rápido e aloca pouco. Desserializa uma resposta típica de 170 moedas bem abaixo de um milissegundo, sem dependências externas.decimalestá embutido na linguagem. Nunca precisa de umBigDecimalexterno para lidar com dinheiro corretamente — e isso importa quando você soma milhares de linhas de faturas convertidas.- Polly é a biblioteca de resiliência padrão. Retries, circuit breakers e timeouts ficam a uma chamada de
AddPolicyHandler. - APIs mínimas e AOT. Subir um microserviço minúsculo de taxas em 30 linhas é hoje realista, e ele inicia em frio em milissegundos.
Se estiver avaliando outros stacks, temos guias paralelos para Node.js, Python, Go e PHP. Este artigo é específico para C# e parte do princípio que você se sente confortável com async/await, generic hosting e injeção de dependência.
Pré-requisitos e configuração do projeto
Precisa do .NET 8 ou .NET 9 instalado. Confirme seu SDK:
dotnet --versionCrie uma nova solução com uma biblioteca de classes para o cliente e um app de console para exercitá-lo:
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.csprojVocê também precisa de uma API key da Finexly. Crie sua conta gratuita — sem cartão de crédito, e o plano gratuito inclui 1.000 requisições por mês, mais que suficiente para desenvolvimento e CI.
Nunca deixe a chave fixa no código. Guarde-a em variável de ambiente:
# macOS / Linux
export FINEXLY_API_KEY="your_api_key_here"# Windows PowerShell
$env:FINEXLY_API_KEY = "your_api_key_here"Em produção, armazene no Azure Key Vault, no AWS Secrets Manager ou em dotnet user-secrets para desenvolvimento local.
Sua primeira requisição com HttpClient
Vamos começar com o exemplo mais simples que funciona. Abra Finexly.ConsoleApp/Program.cs e escreva:
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"]);Execute:
dotnet run --project Finexly.ConsoleAppVocê verá um dicionário de códigos de moeda e suas taxas contra o USD. Funciona, mas tem todos os defeitos esperados num exemplo de 10 linhas: valores object sem tipo, sem cache, sem retries, um HttpClient instanciado manualmente e a API key na URL. Vamos consertar tudo isso.
Definindo modelos de resposta fortemente tipados
System.Text.Json desserializa de forma limpa em tipos record do C#. No projeto Finexly.Client, crie 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
);Alguns detalhes que merecem destaque:
decimalem vez dedouble. Erro de ponto flutuante é inaceitável para dinheiro.decimalte dá 28–29 dígitos significativos e aritmética exata na base 10.IReadOnlyDictionary. A resposta é só-leitura — expresse isso no tipo.DateOnly. O .NET moderno traz um tipo de data adequado. Use-o no lugar deDateTimequando não houver componente de hora.
Agora podemos desserializar sem ginástica com dynamic ou Dictionary<string, object>.
Construindo um HttpClient tipado com IHttpClientFactory
O padrão recomendado em .NET é um cliente tipado registrado via IHttpClientFactory. Ele te dá pooling de conexões, handlers configuráveis e DI fácil. Crie FinexlyClient.cs no projeto 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; }
}Alguns pontos a destacar:
- A API key vai num header, não na URL — mais seguro para logs, proxies e traces.
- Um
Timeoutde 5 segundos evita que uma API de câmbio travada cause um checkout lento. - O cliente recebe um
CancellationTokenem cada método — isso não é negociável no ASP.NET moderno. - Usa
GetFromJsonAsync<T>, que combina o GET, a verificação de status e a desserialização numa única chamada com baixa alocação.
Registrando o cliente em Dependency Injection
No seu Program.cs (Console, ASP.NET Core, Worker — a API é a mesma em todos os lugares), conecte tudo:
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 como serviço transient e fornece a ele um HttpClient pooled vindo da factory. Agora você pode injetar FinexlyClient em qualquer controller, endpoint de API mínima ou serviço em background.
Adicionando retries e circuit breaker com Polly
APIs externas ocasionalmente perdem uma requisição, fazem throttling ou soluçam na camada de rede. Polly é a biblioteca padrão de resiliência do .NET e se integra ao IHttpClientFactory em duas linhas.
Instale o pacote:
dotnet add Finexly.ConsoleApp package Microsoft.Extensions.Http.PollyConfigure um retry com backoff e um 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)));Este único registro reaplica em 5xx, 408 e 429 com backoff exponencial (200ms, 400ms, 800ms) e dispara um circuit breaker se a API estiver completamente fora — assim sua página de checkout degrada graciosamente em vez de manter conexões abertas por minutos.
Cacheando respostas com IMemoryCache
Taxas de câmbio ao vivo raramente mudam mais de uma vez por minuto, então um pequeno cache em processo costuma reduzir as chamadas upstream em 95% ou mais. O .NET já inclui IMemoryCache.
dotnet add Finexly.Client package Microsoft.Extensions.Caching.MemoryCrie 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);
})!;
}
}Registre junto com IMemoryCache:
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<CachedFinexlyClient>();Um TTL de 60 segundos é um ótimo padrão para preços de checkout, previews de faturamento e dashboards administrativos. Para dados históricos, aumente o TTL para horas ou dias — taxas históricas não mudam.
Um conversor de moedas de console funcional
Juntando tudo, aqui está um pequeno CLI que reproduz 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;Faça build de um binário single-file que você pode jogar em qualquer servidor:
dotnet publish Finexly.ConsoleApp -c Release -r linux-x64 \
--self-contained -p:PublishSingleFile=trueExpondo taxas com uma API mínima ASP.NET Core
Para a maioria dos times, a arquitetura certa é um microserviço interno de taxas que envolve a API upstream, adiciona cache e dá a todos os outros serviços um endpoint rápido, gratuito e dentro da VPC. APIs mínimas do ASP.NET Core fazem disso um arquivo de 30 linhas:
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();Faça o deploy atrás do seu API gateway de costume e qualquer serviço da sua frota pode chamar GET /convert?from=USD&to=EUR&amount=99.95 com latência abaixo de 10 milissegundos em cache hit. Para uma experiência no navegador, os mesmos dados estão disponíveis no nosso conversor de moedas hospedado.
Tratando dinheiro corretamente em C#
Alguns padrões defensivos que se pagam logo na primeira auditoria de um livro-caixa de pagamentos:
- Use sempre
decimal, nuncadoubleoufloat.0.1 + 0.2famosamente não é0.3em ponto flutuante — e esse único erro de arredondamento pode te custar uma disputa de chargeback. - Arredonde na apresentação, não no cálculo. Mantenha a precisão completa em cada multiplicação e só
Math.Round(value, 2, MidpointRounding.ToEven)ao exibir ou persistir. - Fixe o modo de arredondamento. Arredondamento bancário (
ToEven) é o padrão da indústria financeira em IEEE 754 e ISO 80000-1. - Trate moedas como códigos opacos. Use strings ISO 4217 — nunca assuma que uma moeda tem duas casas decimais. JPY tem zero, BHD tem três. Nosso guia ISO 4217 cobre os casos extremos.
Boas práticas para produção
Alguns padrões adicionais separam uma integração de brincadeira de um sistema que você pode operar por anos:
- Configure timeouts HTTP curtos. O
HttpClient.Timeoutpadrão de 100 segundos vai travar seu thread pool sob carga. Cinco segundos é mais que suficiente para uma API de câmbio. - Embrulhe toda chamada externa num
CancellationToken. O ASP.NET Core te entrega um de graça; respeite-o para que requisições abortadas não fiquem conversando com o upstream. - Logue códigos de status, não corpos. As respostas da API de câmbio são grandes demais para logar e podem conter dados de taxa que você não quer no Splunk.
- Monitore sua cota. Compare planos de preço e configure alertas no CloudWatch / Application Insights a 80% do seu teto mensal.
- Fixe apenas as moedas que você usa. Enviar
symbols=EUR,GBP,JPYreduz a resposta de ~10 KB para menos de 200 bytes. - Cacheie agressivamente. Checkouts sensíveis à latência podem viver com cache de 30 segundos; dashboards administrativos suportam 5 minutos.
Se estiver avaliando fornecedores, nossa comparação de APIs de câmbio grátis vs pagas percorre precisão, uptime e preços entre as principais opções.
Perguntas frequentes
Qual API de câmbio funciona melhor com C# e .NET?
Qualquer API REST com JSON limpo e uptime estável vai se integrar bem via IHttpClientFactory. A Finexly foi projetada para ser agnóstica de linguagem — os mesmos endpoints são consumidos por clientes Node, Python, Go, Java e .NET sem lock-in de SDK. Veja nossa visão geral da API gratuita de câmbio para contexto.
Devo usar Newtonsoft.Json ou System.Text.Json?
Para código novo em .NET 6+, use System.Text.Json. É 2–3× mais rápido, aloca menos e faz parte da BCL, então não tem pacote extra para manter atualizado. Newtonsoft.Json continua excelente e vale a pena manter em código legado, mas integrações novas de câmbio em C# devem ter System.Text.Json como padrão.
Preciso de um SDK do NuGet ou HttpClient basta? Um cliente tipado escrito à mão (cerca de 50 linhas de C#) é quase sempre a resposta certa. Te dá controle total sobre cache, retries, telemetria e serialização, e evita o risco de versão desalinhada que acompanha qualquer SDK de terceiros. O exemplo deste tutorial está pronto para produção.
Com que frequência devo atualizar as taxas em uma aplicação .NET? Para a maioria dos casos — checkouts de e-commerce, faturamento SaaS, emissão de fatura — um cache de 30 a 60 segundos é seguro. Sistemas de trading obviamente precisam de dados em tempo real, mas para todo o resto, frescor em escala de minuto é invisível para usuários e reduz drasticamente o gasto com a API.
Posso rodar isso em Azure Functions ou AWS Lambda?
Sim. IHttpClientFactory funciona da mesma forma em Azure Functions isoladas e em handlers Lambda com Microsoft.Extensions.Hosting. Para cargas sensíveis a cold start, publique com AOT (PublishAot=true) e sua função iniciará em menos de 100 ms.
decimal é lento comparado a double?
decimal é cerca de 10–20× mais lento que double por operação aritmética, mas uma conversão de moedas faz no máximo um punhado de multiplicações. Você nunca vai medir a diferença em uma carga real — e o ganho de correção é inegociável quando se trata de dinheiro.
Concluindo
Você agora tem um blueprint completo e nível-produção para integrar uma API de taxas de câmbio em C# / .NET: modelos fortemente tipados, um cliente apoiado em IHttpClientFactory, retries e circuit breaker com Polly, cache em processo, um conversor de console e uma API mínima ASP.NET Core. Cada padrão aqui sai direto da orientação oficial da Microsoft e foi testado em código real de fintech em produção.
Pronto para levar taxas em tempo real para a sua aplicação .NET? Obtenha sua API key gratuita da Finexly — sem cartão de crédito. Comece com 1.000 requisições gratuitas por mês e faça upgrade conforme seu produto cresce. Se quiser primeiro ver como a Finexly se compara a outros fornecedores, leia nossa comparação de APIs de câmbio.
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 →