How to use curl in ASP.NET Core
To use curl in ASP.NET Core, add the CurlDotNet package, register a configured LibCurl instance as a singleton in Program.cs, and inject it into your minimal API handlers or controllers. For one-off calls, the static Curl.ExecuteAsync works without any registration.
dotnet add package CurlDotNet
Register LibCurl as a singleton
LibCurl owns one HttpClient, so a single shared instance reuses connections across requests. Configure its defaults once, at startup:
using CurlDotNet.Lib;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(sp => new LibCurl()
.WithBearerToken(builder.Configuration["Upstream:Token"] ?? "")
.WithUserAgent("MyService/1.0")
.WithTimeout(TimeSpan.FromSeconds(10)));
var app = builder.Build();
The With... methods change the instance’s shared defaults. Call them only during registration, never inside a request handler, or concurrent requests will see each other’s settings. Per-request options go in the Action<CurlOptions> argument instead (see below). The container disposes the singleton at shutdown.
Minimal API
using CurlDotNet.Lib;
var app = WebApplication.CreateBuilder(args).Build();
using var curl = new LibCurl();
app.MapGet("/weather/{city}", async (string city, HttpContext ctx) =>
{
var url = $"https://wttr.in/{Uri.EscapeDataString(city)}?format=j1";
var result = await curl.GetAsync(url, cancellationToken: ctx.RequestAborted);
return result.IsSuccess
? Results.Content(result.Body, "application/json")
: Results.StatusCode(502);
});
app.Run();
In a real app, take LibCurl curl as a handler parameter so it comes from the container.
Controller
using CurlDotNet.Lib;
using Microsoft.AspNetCore.Mvc;
[ApiController, Route("api/[controller]")]
public class ReposController(LibCurl curl) : ControllerBase
{
[HttpGet("{owner}/{repo}")]
public async Task<IActionResult> Get(string owner, string repo, CancellationToken ct)
{
var url = $"https://api.github.com/repos/{Uri.EscapeDataString(owner)}/{Uri.EscapeDataString(repo)}";
var result = await curl.GetAsync(url, o => o.Headers["Accept"] = "application/vnd.github+json", ct);
return StatusCode(result.StatusCode, result.Body);
}
}
The lambda adds a header to this call only. LibCurl merges its defaults into a fresh CurlOptions for each call, so per-call changes do not leak.
Using curl strings in a service
Running a copied curl command is fine for fixed requests. Never paste user input into a command string: a value such as x -o /app/appsettings.json would be read as extra curl flags. Use LibCurl or CurlRequestBuilder for anything user-controlled:
using CurlDotNet.Core;
static Task<CurlResult> Search(string userQuery, CancellationToken ct) =>
CurlRequestBuilder
.Get($"https://httpbin.org/get?q={Uri.EscapeDataString(userQuery)}")
.WithTimeout(TimeSpan.FromSeconds(5))
.ExecuteAsync(ct);
Notes
- Always pass
HttpContext.RequestAborted(or the action’sCancellationToken) so abandoned requests stop calling upstream. Cancellation surfaces asOperationCanceledException, which ASP.NET Core already treats as an aborted request. LibCurldoes not throw for network failures: a DNS error or timeout comes back as a result withIsSuccess == falseand theCurlExceptioninresult.Exception. The staticCurlmethods andCurlRequestBuilderthrow instead.- There is no timeout unless you set one, so set one shorter than your own response budget; see timeouts.
- Do not use the synchronous
Curl.Executein request handlers; it blocks a thread-pool thread. - CurlDotNet has no
AddCurl()extension; registerLibCurlor your own wrapper type as shown.