Table of Contents

Class CurlResult

Namespace
CurlDotNet.Core
Assembly
CurlDotNet.dll

🎯 The response from your curl command - everything you need is here!

After running any curl command, you get this object back. It has the status code, response body, headers, and helpful methods to work with the data.

The API is designed to be intuitive - just type what you want to do:

  • Want the body? → result.Body
  • Want JSON? → result.ParseJson<T>() or result.AsJson<T>()
  • Want to save? → result.SaveToFile("path")
  • Want headers? → result.Headers["Content-Type"]
  • Check success? → result.IsSuccess or result.EnsureSuccess()

Quick Example:

var result = await Curl.Execute("curl https://api.github.com/users/octocat");

if (result.IsSuccess)  // Was it 200-299?
{
    var user = result.ParseJson<User>();  // Parse JSON to your type
    result.SaveToFile("user.json");       // Save for later
}
public class CurlResult
Inheritance
CurlResult
Inherited Members

Remarks

Design Philosophy: Every method name tells you exactly what it does. No surprises. If you guess a method name, it probably exists and does what you expect.

Fluent API: Most methods return 'this' so you can chain operations:

result
    .EnsureSuccess()           // Throw if not 200-299
    .SaveToFile("backup.json") // Save a copy
    .ParseJson<Data>()        // Parse and return data

Properties

BinaryData

Binary data for files like images, PDFs, downloads.

When you download non-text files, the bytes are here:

// Download an image
var result = await Curl.Execute("curl https://example.com/logo.png");

if (result.IsBinary)
{
    File.WriteAllBytes("logo.png", result.BinaryData);
    Console.WriteLine($"Saved {result.BinaryData.Length} bytes");
}
public byte[] BinaryData { get; set; }

Property Value

byte[]

Body

The response body as a string - this is your data!

Contains whatever the server sent back: JSON, HTML, XML, plain text, etc.

Common patterns:

// JSON API response (most common)
if (result.Body.StartsWith("{"))
{
    var data = result.ParseJson<MyClass>();
}

// HTML webpage
if (result.Body.Contains("<html"))
{
    result.SaveToFile("page.html");
}

// Plain text
Console.WriteLine(result.Body);

Note: For binary data (images, PDFs), use BinaryData instead.

Note: Can be null for 204 No Content or binary responses.

public string Body { get; set; }

Property Value

string

Command

The original curl command that was executed.

Useful for debugging or retrying:

Console.WriteLine($"Executed: {result.Command}");

// Retry the same command
var retry = await Curl.Execute(result.Command);
public string Command { get; set; }

Property Value

string

Exception

Any exception if the request failed completely.

Only set for network failures, not HTTP errors:

if (result.Exception != null)
{
    // Network/DNS/Timeout failure
    Console.WriteLine($"Failed: {result.Exception.Message}");
}
else if (!result.IsSuccess)
{
    // HTTP error (404, 500, etc)
    Console.WriteLine($"HTTP {result.StatusCode}");
}
public Exception Exception { get; set; }

Property Value

Exception

Headers

All HTTP headers from the response - contains metadata about the response.

Headers tell you things like content type, cache rules, rate limits, etc. Access them like a dictionary (case-insensitive keys).

Get a specific header:

// These all work (case-insensitive):
var type = result.Headers["Content-Type"];
var type = result.Headers["content-type"];
var type = result.Headers["CONTENT-TYPE"];

// Or use the helper:
var type = result.GetHeader("Content-Type");

Check rate limits (common in APIs):

if (result.Headers.ContainsKey("X-RateLimit-Remaining"))
{
    var remaining = int.Parse(result.Headers["X-RateLimit-Remaining"]);
    if (remaining < 10)
        Console.WriteLine("⚠️ Only {0} API calls left!", remaining);
}

Common headers:

  • Content-Type - Format of the data (application/json, text/html)
  • Content-Length - Size in bytes
  • Location - Where you got redirected to
  • Set-Cookie - Cookies to store
  • Cache-Control - How long to cache
public Dictionary<string, string> Headers { get; set; }

Property Value

Dictionary<string, string>

IsBinary

Is this binary data? (images, PDFs, etc.)

Quick check before accessing BinaryData:

if (result.IsBinary)
    File.WriteAllBytes("file.bin", result.BinaryData);
else
    File.WriteAllText("file.txt", result.Body);
public bool IsBinary { get; }

Property Value

bool

IsSuccess

Quick success check - true if status is 200-299.

The easiest way to check if your request worked:

if (result.IsSuccess)
{
    // It worked! Do something with result.Body
}
else
{
    // Something went wrong, check result.StatusCode
}

What's considered success: 200 OK, 201 Created, 204 No Content, etc.

What's NOT success: 404 Not Found, 500 Server Error, etc.

public bool IsSuccess { get; }

Property Value

bool

OutputFiles

Files that were saved (if using -o flag).

Track what files were created:

foreach (var file in result.OutputFiles)
{
    Console.WriteLine($"Saved: {file}");
}
public List<string> OutputFiles { get; set; }

Property Value

List<string>

StatusCode

The HTTP status code - tells you what happened.

Common codes you'll see:

200 = OK, it worked!
201 = Created something new
204 = Success, but no content returned
400 = Bad request (you sent something wrong)
401 = Unauthorized (need to login)
403 = Forbidden (not allowed)
404 = Not found
429 = Too many requests (slow down!)
500 = Server error (their fault, not yours)
503 = Service unavailable (try again later)

Example - Handle different statuses:

switch (result.StatusCode)
{
    case 200: ProcessData(result.Body); break;
    case 404: Console.WriteLine("Not found"); break;
    case 401: RedirectToLogin(); break;
    case >= 500: Console.WriteLine("Server error, retry later"); break;
}
public int StatusCode { get; set; }

Property Value

int

Timings

Detailed timing information (like curl -w).

See how long each phase took:

Console.WriteLine($"DNS lookup: {result.Timings.NameLookup}ms");
Console.WriteLine($"Connect: {result.Timings.Connect}ms");
Console.WriteLine($"Total: {result.Timings.Total}ms");
public CurlTimings Timings { get; set; }

Property Value

CurlTimings

Methods

AppendToFile(string)

Append response to an existing file.

Add to a file without overwriting:

// Log all responses
result.AppendToFile("api-log.txt");

// Build up a file over time
foreach (var url in urls)
{
    var r = await Curl.Execute($"curl {url}");
    r.AppendToFile("combined.txt");
}
public CurlResult AppendToFile(string filePath)

Parameters

filePath string

Returns

CurlResult

AsJsonDynamic()

Parse JSON as dynamic object (when you don't have a class).

Useful for quick exploration or simple JSON structures. This method returns a dynamic object that allows you to access JSON properties without defining a C# class. However, there's no compile-time checking, so prefer ParseJson<T>() with typed classes when possible.

Example:

dynamic json = result.AsJsonDynamic();
Console.WriteLine(json.name);           // Access properties directly
Console.WriteLine(json.users[0].email); // Navigate arrays

// Iterate dynamic arrays
foreach (var item in json.items)
{
    Console.WriteLine(item.title);
}
public dynamic AsJsonDynamic()

Returns

dynamic

A dynamic object representing the JSON. In .NET 6+, this is a JsonDocument. In .NET Standard 2.0, this is a JObject from Newtonsoft.Json.

Access properties like: dynamicObj.propertyName or dynamicObj["propertyName"]

Remarks

⚠️ Warning: No compile-time checking! If a property doesn't exist, you'll get a runtime exception.

For production code, prefer ParseJson<T>() with typed classes for better safety and IntelliSense support.

This method is useful for:

  • Quick prototyping and exploration
  • Working with highly dynamic JSON structures
  • One-off scripts and tools

Exceptions

ArgumentNullException

Thrown when Body is null or empty.

See Also

AsJson<T>()

Parse JSON response (alternative name for ParseJson<T>()).

Some people prefer AsJson, some prefer ParseJson. Both methods are identical and produce the same result.

var data = result.AsJson<MyData>();
// Exactly the same as: result.ParseJson<MyData>()
public T AsJson<T>()

Returns

T

An instance of T with data from the JSON Body.

Type Parameters

T

The type to deserialize to. See ParseJson<T>() for details.

Remarks

This is simply an alias for ParseJson<T>(). Use whichever method name you prefer.

Exceptions

ArgumentNullException

Thrown when Body is null or empty.

InvalidOperationException

Thrown when JSON deserialization fails.

See Also

EnsureContains(string)

Throw if response body doesn't contain expected text.

Validate response content:

// Make sure we got the right response
result.EnsureContains("success");

// Check for error messages
if (result.Body.Contains("error"))
{
    result.EnsureContains("recoverable");  // Make sure it's recoverable
}
public CurlResult EnsureContains(string expectedText)

Parameters

expectedText string

Returns

CurlResult

EnsureStatus(int)

Throw if status doesn't match what you expect.

Validate specific status codes:

// Expect 201 Created
result.EnsureStatus(201);

// Expect 204 No Content
result.EnsureStatus(204);
public CurlResult EnsureStatus(int expectedStatus)

Parameters

expectedStatus int

The status code you expect

Returns

CurlResult

This result if status matches (for chaining)

Exceptions

CurlHttpException

Thrown if status doesn't match

EnsureSuccess()

Throw an exception if the request wasn't successful (not 200-299).

Use this when you expect success and want to fail fast. This matches curl's -f (fail) flag behavior.

public CurlResult EnsureSuccess()

Returns

CurlResult

This result if successful (for chaining)

Examples

// Fail fast pattern
try
{
    var data = result
        .EnsureSuccess()      // Throws if not 200-299
        .ParseJson<Data>();  // Only runs if successful
}
catch (CurlHttpException ex)
{
    Console.WriteLine($"HTTP {ex.StatusCode}: {ex.Message}");
    Console.WriteLine($"Response body: {ex.ResponseBody}");
}

// Common API pattern - get user data
var user = (await Curl.ExecuteAsync("curl https://api.example.com/user/123"))
    .EnsureSuccess()        // Throws on 404, 500, etc.
    .ParseJson<User>();    // Safe to parse, we know it's 200

// Chain multiple operations
var response = await Curl.ExecuteAsync("curl https://api.example.com/data");
var processed = response
    .EnsureSuccess()           // Ensure 200-299
    .SaveToFile("backup.json") // Save backup
    .ParseJson<DataModel>(); // Then parse

// Different handling for different status codes
try
{
    result.EnsureSuccess();
    ProcessData(result.Body);
}
catch (CurlHttpException ex) when (ex.StatusCode == 404)
{
    Console.WriteLine("Resource not found");
}
catch (CurlHttpException ex) when (ex.StatusCode >= 500)
{
    Console.WriteLine("Server error - retry later");
}

Exceptions

CurlHttpException

Thrown if status is not 200-299. The exception contains StatusCode and ResponseBody.

See Also

FilterLines(Func<string, bool>)

Extract lines that match a condition.

Filter text responses:

// Keep only error lines
result.FilterLines(line => line.Contains("ERROR"));

// Remove empty lines
result.FilterLines(line => !string.IsNullOrWhiteSpace(line));

// Keep lines starting with data
result.FilterLines(line => line.StartsWith("data:"));
public CurlResult FilterLines(Func<string, bool> predicate)

Parameters

predicate Func<string, bool>

Returns

CurlResult

GetHeader(string)

Get a specific header value (case-insensitive).

Easy header access with null safety. This matches curl's header behavior exactly.

public string GetHeader(string headerName)

Parameters

headerName string

Name of the header (case doesn't matter)

Returns

string

Header value or null if not found

Examples

// Get content type
var contentType = result.GetHeader("Content-Type");
if (contentType?.Contains("json") == true)
{
    var data = result.ParseJson<MyData>();
}

// Check rate limits (common in APIs)
var remaining = result.GetHeader("X-RateLimit-Remaining");
if (remaining != null && int.Parse(remaining) < 10)
{
    Console.WriteLine("⚠️ Only {0} API calls left!", remaining);
}

// Check cache control
var cacheControl = result.GetHeader("Cache-Control");
if (cacheControl?.Contains("no-cache") == true)
{
    Console.WriteLine("Response should not be cached");
}

// Get redirect location
var location = result.GetHeader("Location");
if (location != null)
{
    Console.WriteLine($"Redirected to: {location}");
}
See Also

HasHeader(string)

Check if a header exists.

Test for header presence before accessing. This is case-insensitive, matching curl's behavior.

public bool HasHeader(string headerName)

Parameters

headerName string

Name of the header to check (case-insensitive)

Returns

bool

true if the header exists, false otherwise

Examples

// Check for cookies
if (result.HasHeader("Set-Cookie"))
{
    var cookie = result.GetHeader("Set-Cookie");
    Console.WriteLine($"Cookie received: {cookie}");
}

// Check for authentication requirements
if (result.HasHeader("WWW-Authenticate"))
{
    Console.WriteLine("Authentication required");
}

// Check for custom headers
if (result.HasHeader("X-Custom-Header"))
{
    var value = result.GetHeader("X-Custom-Header");
    ProcessCustomValue(value);
}

// Conditional logic based on headers
if (result.HasHeader("Content-Encoding") && 
    result.GetHeader("Content-Encoding").Contains("gzip"))
{
    Console.WriteLine("Response is gzip compressed");
}
See Also

ParseJson<T>()

Parse the JSON response into your C# class.

The most common operation - turning JSON into objects. This method uses JsonSerializer in .NET 6+ and Newtonsoft.Json.JsonConvert in .NET Standard 2.0 for maximum compatibility.

Example:

// Define your class matching the JSON structure
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
    public int Id { get; set; }
}

// Parse the response
var user = result.ParseJson<User>();
Console.WriteLine($"Hello {user.Name}!");

// Or parse arrays
var users = result.ParseJson<List<User>>();
Console.WriteLine($"Found {users.Count} users");

Tip: Use https://json2csharp.com to generate C# classes from JSON!

public T ParseJson<T>()

Returns

T

An instance of T with data from the JSON Body.

Type Parameters

T

The type to deserialize to. Must match the JSON structure. Can be a class, struct, or collection type like List<T> or Dictionary<TKey, TValue>.

Remarks

This method automatically detects whether to use System.Text.Json or Newtonsoft.Json based on the target framework.

For complex JSON structures, consider using AsJsonDynamic() for exploration, then creating a typed class.

If T doesn't match the JSON structure, properties that don't match will be left at their default values.

Exceptions

ArgumentNullException

Thrown when Body is null or empty.

InvalidOperationException

Thrown when JSON deserialization fails or JSON doesn't match type T.

See Also

Print()

Print status code and body to console.

More detailed debug output:

result.Print();
// Output:
// Status: 200
// {"name":"John","age":30}
public CurlResult Print()

Returns

CurlResult

PrintBody()

Print the response body to console.

Quick debugging output:

result.PrintBody();  // Just prints the body

// Chain with other operations
result
    .PrintBody()           // Debug output
    .SaveToFile("out.txt") // Also save it
    .ParseJson<Data>();   // Then parse
public CurlResult PrintBody()

Returns

CurlResult

This result (for chaining)

PrintVerbose()

Print everything - status, headers, and body (like curl -v).

Full debug output:

result.PrintVerbose();
// Output:
// Status: 200
// Headers:
//   Content-Type: application/json
//   Content-Length: 123
// Body:
// {"name":"John"}
public CurlResult PrintVerbose()

Returns

CurlResult

Retry()

Retry the same curl command again.

Simple retry for transient failures:

// First attempt
var result = await Curl.Execute("curl https://flaky-api.example.com");

// Retry if it failed
if (!result.IsSuccess)
{
    result = await result.Retry();
}

// Retry with delay
if (result.StatusCode == 429)  // Too many requests
{
    await Task.Delay(5000);
    result = await result.Retry();
}
public Task<CurlResult> Retry()

Returns

Task<CurlResult>

New result from retrying the command

RetryWith(Action<CurlSettings>)

Retry with modifications to the original command.

Retry with different settings:

// Retry with longer timeout
var result = await result.RetryWith(settings =>
{
    settings.Timeout = TimeSpan.FromSeconds(60);
});

// Retry with authentication
var result = await result.RetryWith(settings =>
{
    settings.AddHeader("Authorization", "Bearer " + token);
});
public Task<CurlResult> RetryWith(Action<CurlSettings> configure)

Parameters

configure Action<CurlSettings>

Returns

Task<CurlResult>

SaveAsCsv(string)

Save JSON response as CSV file (for JSON arrays).

Converts JSON arrays to CSV for Excel:

// JSON: [{"name":"John","age":30}, {"name":"Jane","age":25}]
result.SaveAsCsv("users.csv");

// Creates CSV:
// name,age
// John,30
// Jane,25

// Open in Excel
Process.Start("users.csv");

Note: Only works with JSON arrays of objects.

public CurlResult SaveAsCsv(string filePath)

Parameters

filePath string

Returns

CurlResult

SaveAsJson(string, bool)

Save as formatted JSON file (pretty-printed).

Makes JSON human-readable with indentation:

// Save with nice formatting
result.SaveAsJson("data.json");           // Pretty-printed
result.SaveAsJson("data.json", false);    // Minified

// Before: {"name":"John","age":30}
// After:  {
//           "name": "John",
//           "age": 30
//         }
public CurlResult SaveAsJson(string filePath, bool indented = true)

Parameters

filePath string

Where to save the JSON file

indented bool

true for pretty formatting (default), false for minified

Returns

CurlResult

This result (for chaining)

SaveToFile(string)

Save the response to a file - works for both text and binary!

Smart saving - automatically handles text vs binary:

// Save any response
result.SaveToFile("output.txt");     // Text saved as text
result.SaveToFile("image.png");      // Binary saved as binary

// Chain operations (returns this)
result
    .SaveToFile("backup.json")       // Save a backup
    .ParseJson<Data>();              // Then parse it

Path examples:

result.SaveToFile("file.txt");              // Current directory
result.SaveToFile("data/file.txt");         // Relative path
result.SaveToFile(@"C:\temp\file.txt");     // Absolute path
result.SaveToFile("/home/user/file.txt");   // Linux/Mac path
public CurlResult SaveToFile(string filePath)

Parameters

filePath string

Where to save the file

Returns

CurlResult

This result (for chaining)

Examples

// Download and save JSON response
var result = await Curl.ExecuteAsync("curl https://api.example.com/data.json");
result.SaveToFile("data.json");
// File is now saved to disk AND still available in result.Body

// Download image and save
var result = await Curl.ExecuteAsync("curl https://example.com/logo.png");
result.SaveToFile("logo.png");
Console.WriteLine($"Saved {result.BinaryData.Length} bytes");

// Chain with parsing
var result = await Curl.ExecuteAsync("curl https://api.example.com/users");
var users = result
    .SaveToFile("backup-users.json")  // Save backup
    .ParseJson<List<User>>();    // Then parse

// Save with relative path
result.SaveToFile("downloads/report.pdf");

// Save with absolute path
result.SaveToFile(@"C:\Temp\output.txt");  // Windows
result.SaveToFile("/tmp/output.txt");       // Linux/Mac
See Also

SaveToFileAsync(string)

Save the response to a file asynchronously.

Same as SaveToFile but doesn't block:

await result.SaveToFileAsync("large-file.json");

// Or chain async operations
await result
    .SaveToFileAsync("backup.json")
    .ContinueWith(_ => Console.WriteLine("Saved!"));
public Task<CurlResult> SaveToFileAsync(string filePath)

Parameters

filePath string

Returns

Task<CurlResult>

ToStream()

Convert the response to a Stream for reading.

Useful for streaming or processing data:

using var stream = result.ToStream();
using var reader = new StreamReader(stream);
var line = await reader.ReadLineAsync();

// Or for binary data
using var stream = result.ToStream();
var buffer = new byte[1024];
var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
public Stream ToStream()

Returns

Stream

A MemoryStream containing the response data

Transform<T>(Func<CurlResult, T>)

Transform the result using your own function.

Extract or convert data however you need:

// Extract just what you need
var name = result.Transform(r =>
{
    var user = r.ParseJson<User>();
    return user.Name;
});

// Convert to your own type
var summary = result.Transform(r => new
{
    Success = r.IsSuccess,
    Size = r.Body?.Length ?? 0,
    Type = r.GetHeader("Content-Type")
});
public T Transform<T>(Func<CurlResult, T> transformer)

Parameters

transformer Func<CurlResult, T>

Returns

T

Type Parameters

T