Syntrony.HttpClient 2.0.1

dotnet add package Syntrony.HttpClient --version 2.0.1
                    
NuGet\Install-Package Syntrony.HttpClient -Version 2.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Syntrony.HttpClient" Version="2.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Syntrony.HttpClient" Version="2.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Syntrony.HttpClient" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Syntrony.HttpClient --version 2.0.1
                    
#r "nuget: Syntrony.HttpClient, 2.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Syntrony.HttpClient@2.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Syntrony.HttpClient&version=2.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Syntrony.HttpClient&version=2.0.1
                    
Install as a Cake Tool

Syntrony.HttpClient

A reusable HTTP client for Syntrony's .NET services. Centralizes HttpClient configuration via IHttpClientFactory, exposes typed methods that wrap every response in a Result<T> (no exceptions thrown for non-successful HTTP responses), supports multipart/form-data uploads and streaming/binary downloads, and offers optional resilience policies with Polly. The package has no dependency on ASP.NET Core's shared framework — it works from any project type (console, Worker Service, isolated Azure Function, etc.).

Result<T> is Syntrony.Results.Result<T>, defined in the Syntrony core package and pulled in automatically as a transitive dependency. It's the same contract type a Syntrony service returns, so a client and the API it talks to share one shape — depending on it doesn't pull in ASP.NET Core: the core package has no FrameworkReference.

Installation

dotnet add package Syntrony.HttpClient

Breaking change in 2.0.0: Result<T> moved from Syntrony.HttpClient.Wrapper to Syntrony.Results (now owned by the Syntrony core package instead of living in this one). The JSON on the wire is identical and AddSyntronyHttpClient didn't change — update the using and recompile.

Basic usage

using Syntrony.HttpClient;
using Syntrony.HttpClient.Configuration;

builder.Services.AddSyntronyHttpClient(
    client: "PaymentsApiClient",
    configureOptions: options =>
    {
        options.BaseAddress = builder.Configuration["PAYMENTS_API_URL"];
        options.Timeout = int.Parse(builder.Configuration["PAYMENTS_API_TIMEOUT"] ?? "100");
        options.DefaultHeaders = new Dictionary<string, string>
        {
            ["Accept"] = "application/json"
        };
    });

The client name ("PaymentsApiClient" in the example) is what IHttpClientFactory uses internally — if the project only consumes a single external API, it can be omitted and the ServiceExtension.DefaultClientName default is used instead.

Configuration via environment variables

The package does not read environment variables directly. It's the consuming project's responsibility to resolve them (ASP.NET Core already loads environment variables as a configuration provider by default) and pass them into the configureOptions delegate:

builder.Services.AddSyntronyHttpClient(options =>
{
    builder.Configuration.GetSection("PaymentsApi").Bind(options);
});

Consuming the client

using Syntrony.Results;

public class PaymentsService(IHttpSyntronyClient httpClient)
{
    public async Task<Result<PaymentDto>> GetPaymentAsync(string id)
    {
        var headers = new Dictionary<string, string>
        {
            ["Authorization"] = $"Bearer {token}"
        };

        return await httpClient.GetAsync<PaymentDto>($"payments/{id}", headers);
    }
}

All methods (GetAsync, PostAsync, PutAsync, PatchAsync, DeleteAsync) return a Result<TResponse>:

var result = await httpClient.GetAsync<PaymentDto>("payments/123", headers: null);

if (result.IsSuccess)
    return result.Value;

// result.Error contains the error response body
logger.LogWarning("Payments API failed: {Error}", result.Error);

File uploads (multipart/form-data)

Simple cases (plain fields plus byte[]/Stream properties) can go straight through PostAsync with contentType: "multipart/form-data" — the library builds the content via reflection:

public class UploadRequest
{
    public string Description { get; set; }
    public byte[] File { get; set; }
}

await httpClient.PostAsync<UploadRequest, UploadResultDto>(
    "documents",
    new UploadRequest { Description = "Invoice", File = fileBytes },
    headers: null,
    contentType: "multipart/form-data");

For explicit field/file names or multiple files under the same field, use MultipartFormBuilder and pass the resulting HttpContent directly (it's passed through as-is, ignoring contentType):

using Syntrony.HttpClient.Content;

var content = new MultipartFormBuilder()
    .AddField("description", "Invoice")
    .AddFile("file", fileStream, "invoice.pdf")
    .Build();

await httpClient.PostAsync<HttpContent, UploadResultDto>("documents", content, headers: null);

Note: the package no longer references ASP.NET Core's Microsoft.AspNetCore.App shared framework, so it doesn't accept IFormFile directly. In a web project, convert it first: formFile.OpenReadStream() and pass that Stream to MultipartFormBuilder.AddFile.

Downloading files / streaming responses

For large or binary payloads (PDFs, images, etc.), DownloadAsync uses HttpCompletionOption.ResponseHeadersRead so the body is never fully buffered in memory. The caller owns the returned Stream and must dispose it — disposing it releases the underlying connection:

var result = await httpClient.DownloadAsync($"documents/{id}/file");

if (result.IsSuccess)
{
    await using var stream = result.Value!;
    await using var fileOnDisk = File.Create("invoice.pdf");
    await stream.CopyToAsync(fileOnDisk);
}

For custom deserialization straight from the response stream (streaming JSON, CSV, protobuf, etc.) without buffering it as a string first, use the GetAsync overload that takes a Func<Stream, CancellationToken, Task<TResponse>>:

var result = await httpClient.GetAsync<ReportDto>(
    "reports/large",
    (stream, ct) => JsonSerializer.DeserializeAsync<ReportDto>(stream, cancellationToken: ct).AsTask());

Resilience (Polly)

Optional retries with exponential backoff (including client-side timeouts) and circuit breaker, chained on the IHttpClientBuilder returned by AddSyntronyHttpClient:

builder.Services
    .AddSyntronyHttpClient(options => options.BaseAddress = "https://api.example.com")
    .AddSyntronyResilience(retryCount: 3, enableCircuitBreaker: true);

Use cases

  • Consuming Syntrony's internal REST APIs (e.g. evx-pl-sso-ms) with per-request auth headers.
  • Sending files via multipart/form-data (byte[]/Stream, or MultipartFormBuilder for explicit field/file names).
  • Downloading files or large/non-JSON payloads without buffering the full body in memory.
  • HTTP calls with automatic retries on transient errors (5xx, timeouts, HttpRequestException).

Known limitation (v1): the package supports a single Syntrony client per consuming project. Calling AddSyntronyHttpClient more than once with different names does not correctly isolate their options (SyntronyHttpClientOptions is resolved without a name). Support for multiple named clients is planned for v2.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.1 90 9/24/2026
2.0.0 79 9/24/2026
1.0.0 103 8/21/2026