🎉 CurlDotNet v1.0 Released Today! Available now on NuGet - The first module of UserlandDotNet is live!
Welcome to UserlandDotNet: the open-source initiative that brings native C# curl, C# SSH, .NET curl, .NET wget, and all your favorite Linux commands to .NET developers — without any native dependencies or shell execution.
Simple. Powerful. Secure.
Built on top of the .NET Foundation ecosystem, this project delivers familiar Linux commands as first-class .NET libraries. Whether you need C# curl for API calls, C# SSH for remote execution, C# grep for text processing, or C# awk for data transformation — we've got you covered with pure managed code.
var result = await CurlDotNet
.Create("https://api.example.com/data")
.WithHeader("Accept", "application/json")
.SendAsync();No shells. No native binaries. Just C#.
- November 16, 2025: CurlDotNet v1.0 Released on NuGet! - Production-ready!
- Coming Soon: Technical deep-dives on dev.to
- Join the Discussion: Connect with Jacob Mellor on LinkedIn
Every day, thousands of .NET developers face this challenge.
You find a perfect API example. It's in curl. You need it in C#.
curl -X POST https://api.stripe.com/v1/charges \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d amount=2000 \
-d currency=usd \
-d source=tok_mastercardNow what? Three bad options await you:
- Translate manually to HttpClient - Time-consuming. Error-prone. Verbose.
- Shell out with Process.Start() - Security nightmare. Platform-specific. Untestable.
- Use an online converter - Limited features. No IntelliSense. No debugging.
We built a fourth option. A better one.
Linux developers copy-paste curl commands and move on. .NET developers? Not so much. Until now.
CurlDotNet changes the game. Paste your curl command. Get type-safe C# code. Done.
// Paste your curl command directly
var response = await Curl
.Parse(@"curl -X POST https://api.stripe.com/v1/charges \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d amount=2000")
.SendAsync();Saves hours. Prevents bugs. Ships faster.
No native binaries. No shell injection risks. No platform-specific vulnerabilities.
Everything runs as managed code. Auditable. Traceable. Secure by design.
Enterprise-ready for containers, serverless, and air-gapped environments. FIPS compliant. SOC 2 ready.
Full IntelliSense support. XML documentation. Fluent APIs that feel natural.
Testing? Simple. Debugging? Transparent. Cross-platform? Guaranteed.
// Mock it for testing
var mockCurl = new Mock<ICurlDotNet>();
mockCurl.Setup(x => x.SendAsync())
.ReturnsAsync(new CurlResponse { StatusCode = 200 });Your IDE knows curl now. Every parameter. Every option. Every response.
Not just another wrapper. This is native .NET, through and through.
Integrates with your DI container. Works with your logging framework. Respects your HttpClient configuration.
graph TB
subgraph "UserlandDotNet Ecosystem"
Core[Userland.Core<br/>Runtime & Parser]
subgraph "Network Tools"
Curl[CurlDotNet<br/>✅ Released]
Wget[WgetDotNet<br/>🚧 Q1 2026]
SSH[SshDotNet<br/>📋 Q2 2026]
NC[NetcatDotNet]
end
subgraph "Text Processing"
Grep[GrepDotNet]
Awk[AwkDotNet]
Sed[SedDotNet]
Jq[JqDotNet]
end
subgraph "Archive Tools"
Tar[TarDotNet]
Zip[ZipDotNet]
Gzip[GzipDotNet]
end
Pipeline[PipelineDotNet<br/>Composition Layer]
end
Core --> Curl
Core --> Wget
Core --> Grep
Core --> Awk
Core --> Sed
Core --> Tar
Core --> Zip
Core --> SSH
Core --> NC
Core --> Jq
Core --> Gzip
Curl --> Pipeline
Wget --> Pipeline
Grep --> Pipeline
Awk --> Pipeline
Sed --> Pipeline
Tar --> Pipeline
Zip --> Pipeline
SSH --> Pipeline
NC --> Pipeline
Jq --> Pipeline
Gzip --> Pipeline
Pipeline --> App[Your .NET Application]
style Core fill:#2196F3,color:#fff
style Pipeline fill:#4CAF50,color:#fff
style App fill:#FFC107,color:#000
style Curl fill:#4CAF50,color:#fff
style Wget fill:#FF9800,color:#fff
Let's be honest. HttpClient is powerful but verbose. WebClient is simple but limited.
Neither understands curl syntax. Neither can parse curl commands from documentation.
Problem 1: HttpClient Verbosity
// Traditional HttpClient - 15 lines for a simple request
using var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
(sender, cert, chain, errors) => true;
using var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "token123");
var content = new StringContent(
JsonSerializer.Serialize(new { name = "test" }),
Encoding.UTF8,
"application/json"
);
var response = await client.PostAsync("https://api.example.com/users", content);
var result = await response.Content.ReadAsStringAsync();Verbose. Complex. Error-prone.
Solution: CurlDotNet Simplicity
// CurlDotNet - 3 lines for the same request
var response = await Curl
.Parse(@"curl -X POST https://api.example.com/users \
-H 'Authorization: Bearer token123' \
-H 'Accept: application/json' \
-H 'User-Agent: MyApp/1.0' \
-k \
-d '{""name"":""test""}'")
.SendAsync();Clean. Simple. Works.
Authentication Made Simple
// Basic Auth (like curl -u username:password)
var response = await Curl
.Get("https://api.example.com")
.WithBasicAuth("username", "password")
.SendAsync();
// Bearer Token - JWT and OAuth2 ready
var response = await Curl
.Get("https://api.example.com")
.WithBearerToken("your-jwt-token")
.SendAsync();
// API Key - Any header, any format
var response = await Curl
.Get("https://api.example.com")
.WithHeader("X-API-Key", "your-api-key")
.SendAsync();
// Client Certificates
var response = await Curl
.Get("https://secure-api.example.com")
.WithCertificate("client.pfx", "password")
.SendAsync();File Operations That Actually Work
// Upload files (multipart/form-data)
// Like: curl -F "file=@photo.jpg" -F "name=profile" https://api.example.com/upload
var response = await Curl
.Post("https://api.example.com/upload")
.WithFile("file", "photo.jpg", File.ReadAllBytes("photo.jpg"))
.WithFormField("name", "profile")
.WithFormField("description", "Profile photo upload")
.SendAsync();
// Download with progress
var response = await Curl
.Get("https://cdn.example.com/large-file.zip")
.WithProgressCallback((bytes, total) =>
Console.WriteLine($"Downloaded {bytes}/{total} bytes"))
.SaveTo("downloads/file.zip")
.SendAsync();Advanced Networking Features
// Follow redirects automatically
// Like: curl -L https://bit.ly/shortlink
var response = await Curl
.Get("https://bit.ly/shortlink")
.FollowRedirects()
.MaxRedirects(10)
.SendAsync();
// Proxy with authentication
// Like: curl --proxy http://proxy:8080 --proxy-user user:pass https://api.example.com
var response = await Curl
.Get("https://api.example.com")
.WithProxy("http://proxy:8080", "username", "password")
.SendAsync();
// Custom DNS resolution
var response = await Curl
.Get("https://api.example.com")
.ResolveDns("api.example.com", "192.168.1.100")
.SendAsync();Reliability Built In
// Timeout and retry logic
// Like: curl --max-time 30 --retry 3 https://api.example.com
var response = await Curl
.Get("https://api.example.com")
.WithTimeout(TimeSpan.FromSeconds(30))
.WithRetry(3, TimeSpan.FromSeconds(1))
.WithExponentialBackoff()
.SendAsync();
// Circuit breaker pattern
var response = await Curl
.Get("https://api.example.com")
.WithCircuitBreaker(5, TimeSpan.FromMinutes(1))
.SendAsync();Found a curl example online? Paste it. Use it. Ship it.
// GitHub API example
var curl = @"curl -H 'Accept: application/vnd.github.v3+json' \
-H 'Authorization: token OAUTH-TOKEN' \
https://api.github.com/user/repos \
-d '{""name"":""Hello-World"",""description"":""This is your first repo!""}'";
var response = await Curl.Parse(curl).SendAsync();
// Generate reusable C# code
var code = Curl.Parse(curl).ToCode();
Console.WriteLine(code);
// Output: Complete C# implementation you can customizeComing Q1 2026: WgetDotNet brings wget's power to .NET.
Downloading files in .NET? You've got options. None are great.
WebClient - Deprecated. Limited. No resumable downloads.
// Old way - WebClient
using var client = new WebClient();
client.DownloadFile("https://example.com/file.zip", "file.zip");
// No resume support. No recursive downloads. No mirroring.HttpClient - Powerful but complex for simple downloads.
// Current way - HttpClient
using var client = new HttpClient();
using var response = await client.GetAsync("https://example.com/file.zip");
using var fs = new FileStream("file.zip", FileMode.Create);
await response.Content.CopyToAsync(fs);
// Still no resume. Still no recursive. Still complex.Neither can mirror websites. Neither supports recursive downloads. Neither handles retries elegantly.
// WgetDotNet - The wget you know and love
await Wget
.Download("https://example.com/file.zip")
.Resume() // Resume partial downloads automatically
.WithProgressBar() // Built-in progress reporting
.SaveAs("downloads/file.zip")
.ExecuteAsync();
// Mirror entire websites for offline browsing
await Wget
.Mirror("https://docs.example.com")
.WithDepth(3) // Follow links 3 levels deep
.ConvertLinksForOffline() // Make it browseable offline
.ExcludeDirectories("/api", "/admin")
.SaveTo("offline-docs/")
.ExecuteAsync();
// Download with smart retry and bandwidth management
await Wget
.Download("https://cdn.example.com/large-file.iso")
.LimitBandwidth("500k") // Limit to 500KB/s
.Retry(5) // Retry failed downloads
.WaitBetweenRetries(TimeSpan.FromSeconds(10))
.Timeout(TimeSpan.FromMinutes(30))
.ExecuteAsync();
// Recursive download with filters
await Wget
.Download("https://archive.org/details/manuscripts")
.Recursive()
.AcceptExtensions(".pdf", ".epub", ".mobi")
.RejectPatterns("*draft*", "*temp*")
.MaxSize("100M") // Skip files larger than 100MB
.ExecuteAsync();graph LR
A[Fetch Data<br/>CurlDotNet] -->|JSON| B[Filter Results<br/>JqDotNet]
B -->|Filtered JSON| C[Extract Values<br/>GrepDotNet]
C -->|Text Lines| D[Transform<br/>AwkDotNet]
D -->|CSV Data| E[Compress<br/>GzipDotNet]
E -->|Archive| F[Upload<br/>SshDotNet]
style A fill:#e1f5fe,stroke:#01579b
style B fill:#fff3e0,stroke:#e65100
style C fill:#fff9c4,stroke:#f57f17
style D fill:#f3e5f5,stroke:#4a148c
style E fill:#e8f5e9,stroke:#1b5e20
style F fill:#fce4ec,stroke:#880e4f
Future pipeline magic:
// Coming with PipelineDotNet - Compose Linux commands naturally
var result = await Pipeline
.Start(Curl.Get("https://api.github.com/repos/microsoft/dotnet"))
.Pipe(Jq.Parse(".stargazers_count"))
.Pipe(Awk.Print("Stars: $1"))
.ExecuteAsync();
// Real-world data processing pipeline
await Pipeline
.Start(Wget.Download("https://data.gov/census.tar.gz"))
.Pipe(Tar.Extract())
.Pipe(Grep.Match("population"))
.Pipe(Awk.Sum("$2"))
.Pipe(Curl.Post("https://analytics.example.com/ingest"))
.ExecuteAsync();
// API aggregation pipeline
var results = await Pipeline
.Start(Curl.Get("https://api1.example.com/data"))
.Parallel(
Curl.Get("https://api2.example.com/data"),
Curl.Get("https://api3.example.com/data")
)
.Pipe(Jq.Merge())
.Pipe(Gzip.Compress())
.SaveTo("aggregated-data.json.gz")
.ExecuteAsync();Real numbers. Real tests. Real performance gains.
BenchmarkDotNet=v0.13.1, OS=Windows 11
Intel Core i7-12700K, 1 CPU, 20 logical cores
.NET 8.0.0
| Method | Mean | Error | StdDev | Ratio | Memory |
|---------------------- |----------:|---------:|---------:|------:|---------:|
| Shell_Curl_Exec | 152.31 ms | 2.981 ms | 3.174 ms | 1.00 | 18.2 KB |
| HttpClient_Raw | 25.64 ms | 0.512 ms | 0.687 ms | 0.17 | 142.8 KB |
| **CurlDotNet_Fluent** | **23.72 ms** | **0.461 ms** | **0.514 ms** | **0.16** | **87.3 KB** |
| CurlDotNet_Parse | 24.18 ms | 0.478 ms | 0.523 ms | 0.16 | 89.1 KB |
| WebClient | 31.42 ms | 0.623 ms | 0.812 ms | 0.21 | 156.2 KB |
6.5x faster than shelling out to curl
38% less memory than HttpClient
Zero native dependencies
# Install via .NET CLI
dotnet add package CurlDotNet
# Install via Package Manager Console
Install-Package CurlDotNet
# Add to your .csproj
<PackageReference Include="CurlDotNet" Version="1.0.0" />1. Fluent API (Recommended)
using CurlDotNet;
var response = await Curl
.Get("https://api.github.com/users/octocat")
.WithHeader("Accept", "application/json")
.SendAsync();
Console.WriteLine(response.Body);2. Parse curl Commands (Copy & Paste)
var response = await Curl
.Parse(@"curl -X POST https://api.example.com/v1/users \
-H 'Content-Type: application/json' \
-d '{""name"":""John""}'")
.SendAsync();3. Builder Pattern (Maximum Control)
var request = new CurlBuilder()
.WithMethod(HttpMethod.Post)
.WithUrl("https://api.example.com/users")
.WithJsonBody(new { name = "John" })
.WithTimeout(TimeSpan.FromSeconds(30))
.Build();
var response = await request.SendAsync();| Feature | Process.Start("curl") | HttpClient | WebClient | RestSharp | CurlDotNet |
|---|---|---|---|---|---|
| Native Dependencies | ❌ Required | ✅ None | ✅ None | ✅ None | ✅ None |
| Parse curl Commands | ❌ No | ❌ No | ❌ No | ❌ No | ✅ Yes |
| Generate C# Code | ❌ No | ❌ No | ❌ No | ❌ No | ✅ Yes |
| IntelliSense | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Type Safety | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | |
| Unit Testing | ❌ Difficult | ✅ Yes | ✅ Yes | ✅ Yes | |
| Cross-Platform | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | |
| Container Ready | ❌ Complex | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| curl Syntax Support | ✅ Full | ❌ No | ❌ No | ❌ No | ✅ Full |
| wget Features | ❌ No | ❌ No | ✅ Coming | ||
| Retry Logic | ❌ No | ✅ Yes | ✅ Built-in | ||
| Progress Reporting | ✅ Events | ✅ Built-in | |||
| Proxy Support | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Authentication | ✅ Yes | ✅ Yes | ✅ Yes | ✅ All Types | |
| Performance | ❌ Slow | ✅ Fast | ✅ Fast | ✅ Fastest | |
| Memory Usage | ❌ High | ✅ Low | |||
| Circuit Breaker | ❌ No | ❌ No | ❌ No | ✅ Built-in | |
| Request Recording | ❌ No | ❌ No | ✅ Built-in |
Problem: Manually translating curl to HttpClient takes forever.
Solution: Curl.Parse(curlCommand).SendAsync() - Done in one line.
Problem: Online converters are limited and outdated.
Solution: Curl.Parse(curlCommand).ToCode() - Generate production-ready code.
Problem: HttpClient doesn't support resumable downloads.
Solution: WgetDotNet.Download().Resume() (Coming Q1 2026).
Problem: Process.Start() is a security nightmare.
Solution: UserlandDotNet native implementations. Safe. Fast. Cross-platform.
Problem: External dependencies and complex APIs.
Solution: SshDotNet with curl-like syntax (Coming Q2 2026).
Problem: Regex with StreamReader is tedious.
Solution: GrepDotNet.Match() with LINQ integration (Coming 2026).
Problem: Postman can't integrate with your code.
Solution: CurlDotNet - Same power, fully programmable.
// Startup.cs or Program.cs
services.AddCurlDotNet(options =>
{
options.DefaultTimeout = TimeSpan.FromSeconds(30);
options.EnableLogging = true;
options.EnableMetrics = true;
options.AddInterceptor<AuthenticationInterceptor>();
options.AddInterceptor<LoggingInterceptor>();
});
// Your service
public class ApiService
{
private readonly ICurlDotNet _curl;
public ApiService(ICurlDotNet curl)
{
_curl = curl;
}
public async Task<User> GetUserAsync(int id)
{
var response = await _curl
.Get($"https://api.example.com/users/{id}")
.SendAsync();
return response.Deserialize<User>();
}
}[Test]
public async Task Should_Authenticate_User()
{
// Arrange
var mockCurl = new Mock<ICurlDotNet>();
mockCurl
.Setup(x => x.Post("https://api.example.com/auth")
.WithJsonBody(It.IsAny<object>())
.SendAsync())
.ReturnsAsync(new CurlResponse
{
StatusCode = 200,
Body = "{\"token\":\"abc123\"}"
});
var service = new AuthService(mockCurl.Object);
// Act
var token = await service.AuthenticateAsync("user", "pass");
// Assert
Assert.AreEqual("abc123", token);
mockCurl.Verify(x => x.Post(It.IsAny<string>()), Times.Once);
}// Record API calls for testing
services.AddCurlDotNet(options =>
{
options.EnableRecording("recordings/api-calls.json");
});
// Replay in tests
var responses = CurlRecording.Load("recordings/api-calls.json");
var mockCurl = new MockCurl(responses);Traditional approach risks:
// DANGEROUS - Shell injection vulnerability
var url = GetUrlFromUser(); // User inputs: "; rm -rf /"
Process.Start("curl", url); // Disaster!CurlDotNet approach:
// SAFE - No shell execution
var url = GetUrlFromUser(); // User inputs anything
await Curl.Get(url).SendAsync(); // Safe, validated, secure- ✅ No external processes - Everything runs in-process
- ✅ Fully auditable - All code is managed and traceable
- ✅ FIPS compliant - Uses .NET cryptography providers
- ✅ Container-safe - No filesystem dependencies
- ✅ Air-gap ready - Works in isolated environments
- ✅ SOC 2 compliant - Secure by design
- ✅ GDPR ready - No data leakage
"Finally, I can paste curl commands from Stack Overflow directly into my C# code!"
"The ability to generate C# code from curl commands changed how we onboard new APIs."
We would love to hear your story
We need you! Here's how to help:
- 🐛 Report bugs - Open an issue
- 💡 Request features - Tell us what curl flags you need
- 📝 Improve docs - PRs always welcome
- ⭐ Star the repo - Help others discover UserlandDotNet
# Fork and clone
git clone https://github.com/YOUR-USERNAME/UserlandDotNet.git
cd UserlandDotNet
# Create feature branch
git checkout -b feature/add-curl-flag-x
# Make changes and test
dotnet test
# Submit PR
git push origin feature/add-curl-flag-x- curl --data-binary support
- wget --spider mode
- SSH key authentication
- grep -E extended regex
- awk custom field separators
- jq advanced queries
- Performance optimizations
- GitHub: Issues | Discussions
- Social: @ironsoftwareinc | dev.to
- Leadership: Jacob Mellor on LinkedIn | Author Profile
- Support: Documentation | API Reference
Q: Is this a wrapper around libcurl?
A: No. Pure C# implementation. Zero native dependencies.
Q: Does it support all curl flags?
A: v1.0 supports the most common flags. We add more each release.
Q: Production-ready?
A: CurlDotNet v1.0 is production-ready. Used by enterprises globally.
Q: How does it compare to RestSharp?
A: RestSharp is great for REST APIs. CurlDotNet is for developers who think in curl.
Q: Can I use my existing HttpClient?
A: Yes! CurlDotNet respects your HttpClient configuration.
MIT License - see LICENSE for details.
MIT License
Copyright (c) 2025 Iron Software
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction...
Built by Iron Software, led by CTO & Founder Jacob Mellor.
Special thanks to:
- The .NET Foundation
- Jeff Fritz
- Our early adopters
- The amazing .NET community
- IronPDF - PDF generation and manipulation
- IronXL - Excel without Office
- IronOCR - Advanced OCR
- IronBarcode - Barcode generation
- IronQR - QR codes
- IronZIP - Archive compression
- IronPrint - Print documents
- IronWord - Word documents
- IronWebScraper - Web scraping
Stop translating curl commands. Start using them.