Skip to the content.

Tutorial 5: Understanding the CurlResult Response in C#

Every CurlDotNet request returns a CurlResult, and understanding the CurlResult response in C# (its StatusCode, Body, IsSuccess and Headers) is the foundation for everything that follows in these tutorials. This page walks through each property and the patterns you will use most.

The CurlResult Object

Run any curl command string and you get a CurlResult back. The same object comes back from the fluent CurlRequestBuilder.

using CurlDotNet;
using CurlDotNet.Core;

CurlResult result = await Curl.ExecuteAsync("curl https://httpbin.org/get");

// The builder returns the same type
CurlResult same = await CurlRequestBuilder.Get("https://httpbin.org/get").ExecuteAsync();

Key Properties

IsSuccess

IsSuccess is true when the server answered with a 2xx status code. HTTP errors such as 404 or 500 do not throw; you get a result with IsSuccess == false.

var result = await Curl.ExecuteAsync("curl https://httpbin.org/status/404");

if (result.IsSuccess)
{
    Console.WriteLine(result.Body);
}
else
{
    Console.WriteLine($"Request failed with HTTP {result.StatusCode}");
}

Body

Body holds what curl would have printed to standard output, as a string. For a plain request that is the response body; for JSON APIs it is the raw JSON text you will parse in Tutorial 7. Options change it the way they change curl’s output: -i puts the response headers in front of the body, -I gives you only the headers, -v adds the request/response log, -w appends its text after the body, and -o/-O send the body to a file and leave Body empty.

var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");

string responseBody = result.Body;
Console.WriteLine(responseBody.Length + " characters received");

StatusCode

StatusCode is a plain int holding the HTTP status code the server returned.

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

Console.WriteLine($"Server returned: {result.StatusCode}");

if (result.StatusCode == 404)
    Console.WriteLine("The requested resource was not found.");
else if (result.StatusCode == 401)
    Console.WriteLine("Authentication required.");

Headers

Headers is a case-insensitive Dictionary<string, string> of response headers, with values exactly as the server sent them (for example Content-Encoding and Content-Length still describe the compressed body after --compressed has decoded it). GetHeader returns null instead of throwing when a header is missing, and HasHeader checks for presence.

var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");

string contentType = result.GetHeader("Content-Type");
Console.WriteLine($"Response type: {contentType}");

if (result.HasHeader("Date"))
    Console.WriteLine($"Server time: {result.GetHeader("date")}");

foreach (var header in result.Headers)
    Console.WriteLine($"{header.Key}: {header.Value}");

What About Network Errors?

There is no error string on the result. Network-level failures (DNS, connection refused, timeouts) never produce a CurlResult; they throw an exception derived from CurlException, carrying curl’s exit code, which Tutorial 6 covers in detail. The same happens for HTTP errors when you add -f/--fail (CurlHttpReturnedErrorException, exit 22).

using CurlDotNet.Exceptions;

try
{
    var result = await Curl.ExecuteAsync("curl -m 5 https://no-such-host.invalid/");
}
catch (CurlException ex)
{
    Console.WriteLine($"Request never completed: {ex.Message}");
}

Common Patterns

Pattern 1: Simple Success Check

var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");

if (result.IsSuccess)
    Console.WriteLine(result.Body);
else
    Console.WriteLine($"HTTP {result.StatusCode}");

Pattern 2: Detailed Status Handling

var result = await Curl.ExecuteAsync("curl https://api.github.com/repos/dotnet/runtime");

switch (result.StatusCode)
{
    case 200: Console.WriteLine(result.Body); break;
    case 304: Console.WriteLine("Content has not changed"); break;
    case 404: Console.WriteLine("Resource not found"); break;
    case 401: Console.WriteLine("Please authenticate"); break;
    default:  Console.WriteLine($"Unexpected status: {result.StatusCode}"); break;
}

Pattern 3: Throw on Failure with EnsureSuccess

EnsureSuccess() throws CurlDotNet.Core.CurlHttpException for any non-2xx status and returns the result otherwise, so it chains nicely.

try
{
    var body = (await Curl.ExecuteAsync("curl https://httpbin.org/status/500"))
        .EnsureSuccess()
        .Body;
}
catch (CurlDotNet.Core.CurlHttpException ex)
{
    Console.WriteLine($"HTTP {ex.StatusCode}: {ex.ResponseBody}");
}

Pattern 4: Working with Headers

var result = await Curl.ExecuteAsync("curl https://api.github.com/rate_limit");

string contentType = result.GetHeader("Content-Type") ?? "";
if (contentType.Contains("application/json"))
    Console.WriteLine("JSON response, safe to parse");

string remaining = result.GetHeader("X-RateLimit-Remaining");
if (remaining != null)
    Console.WriteLine($"API calls remaining: {remaining}");

Pattern 5: Response Time Tracking

Timings exposes the same phases curl reports with -w, in milliseconds.

var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");

Console.WriteLine($"DNS lookup:  {result.Timings.NameLookup} ms");
Console.WriteLine($"Connect:     {result.Timings.Connect} ms");
Console.WriteLine($"First byte:  {result.Timings.StartTransfer} ms");
Console.WriteLine($"Total:       {result.Timings.Total} ms");

Working with Different Content Types

JSON Responses

ParseJson<T>() deserializes Body into your class. Property matching is case-insensitive, so login binds to Login; snake_case keys such as public_repos need [JsonPropertyName]. It throws InvalidOperationException when the body is empty or not valid JSON for that type.

using System.Text.Json.Serialization;

public class GitHubUser
{
    public string Login { get; set; }
    [JsonPropertyName("public_repos")] public int PublicRepos { get; set; }
}

var result = await Curl.ExecuteAsync("curl https://api.github.com/users/octocat");
var user = result.ParseJson<GitHubUser>();
Console.WriteLine($"{user.Login} has {user.PublicRepos} public repos");

XML Responses

using System.Xml.Linq;

var result = await Curl.ExecuteAsync("curl https://httpbin.org/xml");
var doc = XDocument.Parse(result.Body);
Console.WriteLine(doc.Root.Name);

Binary Data

When the Content-Type is not text, IsBinary is true and the bytes are in BinaryData. SaveToFile writes either text or binary content.

var result = await Curl.ExecuteAsync("curl https://httpbin.org/image/png");

if (result.IsBinary)
{
    Console.WriteLine($"{result.BinaryData.Length} bytes");
    result.SaveToFile("image.png");
}

Best Practices

1. Check IsSuccess Before Using Body

var result = await Curl.ExecuteAsync("curl https://httpbin.org/status/404");

// Good
if (result.IsSuccess)
    Console.WriteLine(result.Body);

// Risky: Body may be an HTML error page, not the data you expected
Console.WriteLine(result.Body);

2. Log Both Success and Failure

var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");

if (result.IsSuccess)
    logger.LogInformation("GET succeeded in {Ms} ms", result.Timings.Total);
else
    logger.LogError("GET failed with HTTP {Status}", result.StatusCode);

3. Use Status Codes for Business Logic

var result = await Curl.ExecuteAsync("curl https://api.github.com/users/no-such-user-xyz");

if (result.StatusCode == 404)
    return null;              // expected: the user does not exist

return result.EnsureSuccess().ParseJson<GitHubUser>();

4. Prefer the Built-in Helpers

ParseJson<T>(), GetHeader(), EnsureSuccess() and SaveToFile() already cover the common cases, so you rarely need your own extension methods on CurlResult.

Common Mistakes to Avoid

Mistake 1: Parsing Without Checking the Status

// Wrong: a 500 error page is not JSON
var data = result.ParseJson<GitHubUser>();

// Right
if (result.IsSuccess)
{
    var data = result.ParseJson<GitHubUser>();
}

Mistake 2: Treating All Failures the Same

if (result.StatusCode == 429)
{
    // Too many requests: wait and try again later
}
else if (result.StatusCode == 401)
{
    // Refresh credentials
}
else if (!result.IsSuccess)
{
    throw new InvalidOperationException($"HTTP {result.StatusCode}");
}

Mistake 3: Indexing Headers Directly

// Wrong: throws KeyNotFoundException when the header is absent
var etag = result.Headers["ETag"];

// Right: returns null when absent
var etag = result.GetHeader("ETag");

Summary

The CurlResult object gives you everything you need to handle an HTTP response:

What’s Next?

In the next tutorial, we look at handling errors: the exceptions CurlDotNet throws for network failures and how to retry safely.

See also: CurlResult in the API guide and the simple GET recipe.


Previous: Your First Request Next: Handling Errors