返回博客

C# / .NET 汇率 API 完整集成教程(2026)

V
Vlado Grigirov
May 26, 2026
C# .NET Currency API Exchange Rates Tutorial Fintech ASP.NET Core

如果你正在 Microsoft 技术栈上构建支付后端、SaaS 计费引擎或企业定价服务,那么在本周交付一个 C# / .NET 汇率 API 集成,将是你能完成的最高价值任务之一。现代 .NET 已经为你准备好了一切——HttpClientIHttpClientFactorySystem.Text.JsonIMemoryCache 以及一流的依赖注入——可是大多数教程仍然只展示一个 WebClient.DownloadString 片段,这种代码根本通不过任何生产环境的代码评审。本文带你从第一个请求开始,一直走到一个强类型、带缓存、具备弹性的客户端,可以放进 ASP.NET Core、Blazor 应用、WPF 桌面工具或 Azure Function。

读完本教程后,你将拥有一个可复用的 Finexly 客户端,可调用 Finexly API 文档,一个小型的控制台货币转换器,以及一个 ASP.NET Core 极简 API,为整个技术栈提供实时汇率——所有代码都遵循 Microsoft 官方推荐的 .NET 8 / .NET 9 惯用模式。

为什么 C# / .NET 是货币集成的强大选择

生产环境中的货币转换主要是个 I/O 问题:获取汇率、缓存它们、做一些小数乘法、返回 JSON。过去十年里 .NET 运行时一直在为这种工作负载做优化:

  • IHttpClientFactory 解决套接字耗尽问题。 它会复用 HttpMessageHandler 实例,你可以在任何地方注入 HttpClient 而不必担心套接字泄漏——这是我们在老式 .NET 代码中看到的最大间歇性故障来源。
  • System.Text.Json 速度快、分配少。 反序列化一个包含 170 种货币的典型响应远低于 1 毫秒,不需要任何第三方依赖。
  • decimal 是语言内置类型。 处理金额时根本不需要第三方 BigDecimal——当你对数千行折算后的发票求和时,这一点至关重要。
  • Polly 是事实上的弹性库。 重试、断路器和超时,只需一次 AddPolicyHandler 调用。
  • 极简 API 和 AOT。 用 30 行代码搭一个汇率微服务在今天是现实的,并且冷启动只需毫秒。

如果你在评估其他技术栈,我们有平行的指南:Node.jsPythonGoPHP。本文专门针对 C#,并假定你熟悉 async/await、通用主机和依赖注入。

前置条件与项目搭建

你需要安装 .NET 8 或 .NET 9。确认你的 SDK:

dotnet --version

创建一个新的解决方案,包含一个客户端类库和一个用于演练的控制台应用:

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.csproj

你还需要一个 Finexly API key。免费注册——无需信用卡,免费套餐每月 1000 次请求,足以支撑开发和 CI。

千万不要把 key 硬编码在代码里。把它存为环境变量:

# macOS / Linux
export FINEXLY_API_KEY="your_api_key_here"
# Windows PowerShell
$env:FINEXLY_API_KEY = "your_api_key_here"

在生产环境中,把它存到 Azure Key Vault、AWS Secrets Manager,或本地开发时用 dotnet user-secrets

使用 HttpClient 发送你的第一个请求

让我们从最简单的可运行示例开始。打开 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"]);

运行它:

dotnet run --project Finexly.ConsoleApp

你会看到一个字典,显示各种货币代码及其相对于 USD 的汇率。这能工作,但它具有 10 行示例代码所有的坏味道:object 无类型、无缓存、无重试、手动实例化 HttpClient,以及 API key 暴露在 URL 中。我们要逐一修复这些问题。

定义强类型响应模型

System.Text.Json 可以干净地反序列化为 C# 的 record 类型。在 Finexly.Client 项目中,创建 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
);

几个值得注意的细节:

  • decimal 而不是 double 浮点误差对金额来说是不可接受的。decimal 提供 28–29 位有效数字以及精确的十进制运算。
  • IReadOnlyDictionary 响应是只读的——在类型上体现这一点。
  • DateOnly 现代 .NET 自带了正经的日期类型。当不需要时间分量时,用它替代 DateTime

现在我们可以反序列化了,不需要任何 dynamicDictionary<string, object> 的怪操作。

使用 IHttpClientFactory 构建强类型 HttpClient

.NET 推荐的模式是通过 IHttpClientFactory 注册的强类型客户端。它带来连接池、可配置的 handler 和便捷的 DI。在 Finexly.Client 项目里创建 FinexlyClient.cs:

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; }
}

几点说明:

  • API key 放在请求头而不是 URL 里——对日志、代理和链路追踪更安全。
  • 5 秒的 Timeout 防止一个卡住的汇率 API 把缓慢的结账过程拖死。
  • 客户端的每个方法都接受 CancellationToken——这在现代 ASP.NET 中是不可妥协的。
  • 它使用 GetFromJsonAsync<T>,在一次低分配的调用中合并了 GET、状态检查和反序列化。

在依赖注入中注册客户端

在你的 Program.cs(控制台、ASP.NET Core、Worker——API 都一样)中接好它:

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>()FinexlyClient 注册为 transient 服务,并由工厂为它提供池化的 HttpClient。现在你可以把 FinexlyClient 注入到任意控制器、极简 API 端点或后台服务中。

用 Polly 添加重试与断路器

外部 API 偶尔会丢请求、做节流,或者在网络层打个嗝。Polly 是 .NET 标准的弹性库,用两行代码就能集成到 IHttpClientFactory

安装包:

dotnet add Finexly.ConsoleApp package Microsoft.Extensions.Http.Polly

配置带回退的重试和断路器:

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)));

这次注册会在 5xx、408 和 429 上以指数回退(200ms、400ms、800ms)进行重试,并在 API 完全宕机时触发断路器——这样你的结账页可以优雅降级,而不是把连接挂住几分钟。

用 IMemoryCache 缓存响应

实时汇率很少每分钟变动多于一次,因此一个小的进程内缓存通常能减少 95% 以上的上游调用。.NET 自带 IMemoryCache

dotnet add Finexly.Client package Microsoft.Extensions.Caching.Memory

创建 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);
        })!;
    }
}

IMemoryCache 一起注册:

builder.Services.AddMemoryCache();
builder.Services.AddSingleton<CachedFinexlyClient>();

对于结账定价、账单预览和管理后台,60 秒的 TTL 是非常好的默认值。对于历史数据,把 TTL 调到几小时甚至几天——历史汇率不会变。

一个可运行的控制台货币转换器

把所有东西组合起来,这里是一个小型 CLI,效果类似 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;

构建一个可以丢到任意服务器上的单文件二进制:

dotnet publish Finexly.ConsoleApp -c Release -r linux-x64 \
  --self-contained -p:PublishSingleFile=true

用 ASP.NET Core 极简 API 对外暴露汇率

对大多数团队而言,正确的架构是一个内部汇率微服务,它包装上游 API、添加缓存,并为其他所有服务提供一个快速、免费、VPC 内的端点。ASP.NET Core 极简 API 把这件事变成了一个 30 行的文件:

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();

把它部署在你常用的 API 网关后面,你的整个服务集群中任意服务都能调用 GET /convert?from=USD&to=EUR&amount=99.95,缓存命中时延迟低于 10 毫秒。对于浏览器端体验,相同的数据可在我们托管的货币转换器中获取。

在 C# 中正确处理金额

一些防御性模式,第一次审计支付账本时就能让你回本:

  • 永远用 decimal,不要用 doublefloat 0.1 + 0.2 在浮点中众所周知不等于 0.3——那一次舍入误差可能让你输掉一次拒付争议。
  • 在展示时取整,而不是在计算时。 在每次乘法中保留完整精度,只有在展示或持久化时才 Math.Round(value, 2, MidpointRounding.ToEven)
  • 固定舍入模式。 银行家舍入(ToEven)是 IEEE 754 和 ISO 80000-1 中金融行业的默认。
  • 把货币当作不透明代码看待。 使用 ISO 4217 字符串——不要假设每种货币都有两位小数。JPY 是零位,BHD 是三位。我们的 ISO 4217 指南涵盖了边界情况。

生产环境最佳实践

还有一些额外模式,能区分玩具项目和你可以维护数年的系统:

  • 设置紧凑的 HTTP 超时。 默认的 100 秒 HttpClient.Timeout 在高负载下会阻塞线程池。5 秒对汇率 API 完全足够。
  • 每个外部调用都包一个 CancellationToken ASP.NET Core 免费给你传入一个;尊重它,这样掉线的请求不会继续和上游通信。
  • 记录状态码,不记录响应体。 汇率 API 响应过大,不便记录,也可能包含你不想放进 Splunk 的汇率数据。
  • 监控你的配额。 比较价格方案,在 CloudWatch / Application Insights 上设置每月上限 80% 的告警。
  • 固定你真正使用的货币。 发送 symbols=EUR,GBP,JPY 能把响应从 ~10 KB 缩小到 200 字节以下。
  • 激进地缓存。 对延迟敏感的结账可以用 30 秒缓存;管理后台可以用 5 分钟。

如果你在评估服务商,我们的免费 vs 付费货币 API 比较涵盖了主流方案在准确性、可用性和价格上的差异。

常见问题

哪种货币 API 最适合 C# 和 .NET? 任何具有干净 JSON 和稳定可用性的 REST API 都能通过 IHttpClientFactory 良好集成。Finexly 专门设计成语言无关——同一组端点被 Node、Python、Go、Java 和 .NET 客户端消费,没有 SDK 绑定。查看我们的免费货币 API 概览了解更多。

应该用 Newtonsoft.Json 还是 System.Text.Json? 对于 .NET 6+ 上的新代码,使用 System.Text.Json。它快 2–3 倍、分配更少、属于基础类库,因此没有额外包要维护。Newtonsoft.Json 仍然很好,值得在遗留代码中保留,但全新的 C# 货币集成应当默认使用 System.Text.Json

我需要一个 NuGet SDK 还是 HttpClient 就够? 一个手写的强类型客户端(大约 50 行 C# 代码)几乎总是正确答案。它让你完全掌控缓存、重试、遥测和序列化,并避免任何第三方 SDK 的版本漂移风险。本教程中的示例已经可用于生产。

.NET 应用中应多久刷新一次汇率? 对大多数场景——电商结账、SaaS 计费、开票——30 到 60 秒的缓存是安全的。交易系统当然需要实时数据,但其他一切场景,分钟级的新鲜度对用户来说是不可见的,并能大幅降低 API 开销。

我可以在 Azure Functions 或 AWS Lambda 中运行吗? 可以。IHttpClientFactory 在隔离进程的 Azure Functions 和使用 Microsoft.Extensions.Hosting 的 Lambda handler 中工作方式相同。对冷启动敏感的负载,启用 AOT 发布(PublishAot=true),函数将在 100 毫秒内启动。

decimaldouble 相比慢吗? decimal 每次算术运算大约比 double 慢 10–20 倍,但一次货币转换最多只做几次乘法。在真实负载中你永远测不出差别——而对金额来说,正确性的提升是不可妥协的。

总结

你现在拥有了一个完整且生产级的 C# / .NET 汇率 API 集成蓝图:强类型模型、由 IHttpClientFactory 支撑的客户端、基于 Polly 的重试和断路器、进程内缓存、控制台转换器,以及 ASP.NET Core 极简 API。这里的每个模式都直接来自 Microsoft 官方指南,并已在真实的金融科技生产代码中得到验证。

准备好把实时汇率交付到你的 .NET 应用了吗?免费获取 Finexly API key——无需信用卡。从每月 1000 次免费请求开始,随着产品增长再升级。如果你想先了解 Finexly 与其他服务商的对比,可以读我们的货币 API 比较

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 →