Ecng.Net.Clients 1.0.315

dotnet add package Ecng.Net.Clients --version 1.0.315
                    
NuGet\Install-Package Ecng.Net.Clients -Version 1.0.315
                    
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="Ecng.Net.Clients" Version="1.0.315" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ecng.Net.Clients" Version="1.0.315" />
                    
Directory.Packages.props
<PackageReference Include="Ecng.Net.Clients" />
                    
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 Ecng.Net.Clients --version 1.0.315
                    
#r "nuget: Ecng.Net.Clients, 1.0.315"
                    
#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 Ecng.Net.Clients@1.0.315
                    
#: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=Ecng.Net.Clients&version=1.0.315
                    
Install as a Cake Addin
#tool nuget:?package=Ecng.Net.Clients&version=1.0.315
                    
Install as a Cake Tool

Ecng.Net.Clients

A powerful .NET library that provides utility base classes for building REST API clients with built-in serialization, error handling, retry policies, and caching support.

Table of Contents

Overview

Ecng.Net.Clients wraps HttpClient with serialization and error handling, allowing your API client classes to stay clean and concise. Instead of writing repetitive HTTP client code, you can focus on defining your API endpoints.

Why Use This Library?

Standard .NET Approach:

using var client = new HttpClient { BaseAddress = new Uri("https://api.example.com/") };
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", token);

var response = await client.GetAsync("users/123");
response.EnsureSuccessStatusCode();
var user = JsonSerializer.Deserialize<User>(
    await response.Content.ReadAsStringAsync());

Using Ecng.Net.Clients:

public class MyApiClient : RestBaseApiClient
{
    public MyApiClient(string token)
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");
        AddAuthBearer(token);
    }

    public Task<User> GetUser(int id, CancellationToken cancellationToken = default)
        => GetAsync<User>(GetCurrentMethod(), cancellationToken, id);
}

// Usage
var client = new MyApiClient(token);
var user = await client.GetUser(123);

Installation

Add a reference to the Ecng.Net.Clients project or include the NuGet package (if published):

<ItemGroup>
  <ProjectReference Include="..\..\Ecng\Net.Clients\Net.Clients.csproj" />
</ItemGroup>

Quick Start

Creating a Basic REST API Client

using Ecng.Net;
using System.Net.Http.Formatting;

public class GitHubApiClient : RestBaseApiClient
{
    public GitHubApiClient(string accessToken = null)
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.github.com/");

        if (!string.IsNullOrEmpty(accessToken))
            AddAuthBearer(accessToken);

        // Configure retry policy
        RetryPolicy.ReadMaxCount = 3;
        RetryPolicy.WriteMaxCount = 2;
    }

    // GET request: GET /users/{username}
    public Task<GitHubUser> GetUser(string username, CancellationToken cancellationToken = default)
        => GetAsync<GitHubUser>(GetCurrentMethod(), cancellationToken, username);

    // POST request: POST /repos/{owner}/{repo}/issues
    public Task<Issue> CreateIssue(string owner, string repo, CreateIssueRequest request,
        CancellationToken cancellationToken = default)
        => PostAsync<Issue>(GetCurrentMethod(), cancellationToken, owner, repo, request);

    // PUT request: PUT /user/starred/{owner}/{repo}
    public Task StarRepository(string owner, string repo, CancellationToken cancellationToken = default)
        => PutAsync<VoidType>(GetCurrentMethod(), cancellationToken, owner, repo);

    // DELETE request: DELETE /user/starred/{owner}/{repo}
    public Task UnstarRepository(string owner, string repo, CancellationToken cancellationToken = default)
        => DeleteAsync<VoidType>(GetCurrentMethod(), cancellationToken, owner, repo);
}

// Usage
var client = new GitHubApiClient("your_access_token");
var user = await client.GetUser("octocat");
Console.WriteLine($"Name: {user.Name}, Followers: {user.Followers}");

Core Features

REST API Client

The RestBaseApiClient abstract base class provides the foundation for building REST API clients.

Constructor Parameters
public abstract class RestBaseApiClient(
    HttpMessageInvoker http,           // HttpClient or custom message invoker
    MediaTypeFormatter request,        // Formatter for request serialization
    MediaTypeFormatter response)       // Formatter for response deserialization
HTTP Methods

The base class provides protected methods for all standard HTTP verbs:

// GET request
protected Task<TResult> GetAsync<TResult>(
    string methodName,
    CancellationToken cancellationToken,
    params object[] args)

// POST request
protected Task<TResult> PostAsync<TResult>(
    string methodName,
    CancellationToken cancellationToken,
    params object[] args)

// PUT request
protected Task<TResult> PutAsync<TResult>(
    string methodName,
    CancellationToken cancellationToken,
    params object[] args)

// DELETE request
protected Task<TResult> DeleteAsync<TResult>(
    string methodName,
    CancellationToken cancellationToken,
    params object[] args)
URL Construction

By default, method names are automatically converted to URL paths:

  • GetUserAsyncgetuser
  • CreateOrdercreateorder

Arguments are added as:

  • GET/DELETE: Query string parameters
  • POST/PUT: Request body

Authentication

The library supports multiple authentication schemes:

Bearer Token Authentication
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient(string token)
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");
        AddAuthBearer(token);  // Adds "Authorization: Bearer {token}"
    }
}
Basic Authentication
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient(string username, string password)
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");

        var credentials = Convert.ToBase64String(
            Encoding.UTF8.GetBytes($"{username}:{password}"));
        AddAuth(AuthenticationSchemes.Basic, credentials);
    }
}
Custom Authentication
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient(string apiKey)
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");
        AddAuth("X-API-Key", apiKey);
    }
}
Per-Request Headers
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");

        // Headers added to every request
        PerRequestHeaders["User-Agent"] = "MyApp/1.0";
        PerRequestHeaders["Accept-Language"] = "en-US";
    }
}

Request/Response Handling

JSON Serialization (Default)
using System.Net.Http.Formatting;
using Newtonsoft.Json;

var formatter = new JsonMediaTypeFormatter
{
    SerializerSettings = new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore,
        DateFormatString = "yyyy-MM-dd",
        Converters = { new StringEnumConverter() }
    }
};

public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), formatter, formatter)
    {
        BaseAddress = new Uri("https://api.example.com/");
    }
}
Form URL Encoded

For APIs that expect form-urlencoded data:

using Ecng.Net;

public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(
            new HttpClient(),
            new RestApiFormUrlEncodedMediaTypeFormatter(),  // Request
            new JsonMediaTypeFormatter())                    // Response
    {
        BaseAddress = new Uri("https://api.example.com/");
    }

    public Task<TokenResponse> GetToken(string username, string password,
        CancellationToken cancellationToken = default)
        => PostAsync<TokenResponse>(GetCurrentMethod(), cancellationToken, username, password);
}

// Sends: username=john&password=secret123
Plain Text Responses

For APIs that return plain text:

using Ecng.Net;

public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(
            new HttpClient(),
            new JsonMediaTypeFormatter(),
            new TextMediaTypeFormatter(new[] { "text/plain", "text/html" }))
    {
        BaseAddress = new Uri("https://api.example.com/");
    }

    public Task<string> GetPlainText(CancellationToken cancellationToken = default)
        => GetAsync<string>(GetCurrentMethod(), cancellationToken);
}

Retry Policies

Built-in retry mechanism with exponential backoff:

public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");

        // Configure retry policy
        RetryPolicy.ReadMaxCount = 5;   // Retry GET requests up to 5 times
        RetryPolicy.WriteMaxCount = 2;  // Retry POST/PUT/DELETE up to 2 times

        // RetryPolicy uses exponential backoff by default
    }
}

The RetryPolicyInfo class automatically handles:

  • Network failures
  • Timeout exceptions
  • Server errors (5xx status codes)
  • Exponential backoff between retries

Caching

Cache API responses to reduce network calls:

In-Memory Cache
using Ecng.Net;

public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");

        // Cache GET requests for 5 minutes
        Cache = new InMemoryRestApiClientCache(TimeSpan.FromMinutes(5));
    }
}
Custom Cache Implementation
public class RedisRestApiClientCache : IRestApiClientCache
{
    private readonly IConnectionMultiplexer _redis;

    public RedisRestApiClientCache(IConnectionMultiplexer redis)
    {
        _redis = redis;
    }

    public bool TryGet<T>(HttpMethod method, Uri uri, object body, out T value)
    {
        var db = _redis.GetDatabase();
        var key = $"{method}:{uri}";
        var cached = db.StringGet(key);

        if (cached.HasValue)
        {
            value = JsonConvert.DeserializeObject<T>(cached);
            return true;
        }

        value = default;
        return false;
    }

    public void Set<T>(HttpMethod method, Uri uri, object body, T value)
    {
        var db = _redis.GetDatabase();
        var key = $"{method}:{uri}";
        var serialized = JsonConvert.SerializeObject(value);
        db.StringSet(key, serialized, TimeSpan.FromMinutes(10));
    }

    public void Remove(HttpMethod method = default, string uriLike = default,
        ComparisonOperator op = ComparisonOperator.Greater)
    {
        // Implementation for cache invalidation
    }
}

// Usage
var client = new MyApiClient
{
    Cache = new RedisRestApiClientCache(redisConnection)
};
Cache Invalidation
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");
        Cache = new InMemoryRestApiClientCache(TimeSpan.FromMinutes(5));
    }

    public async Task UpdateUser(int userId, UserUpdateRequest request,
        CancellationToken cancellationToken = default)
    {
        await PutAsync<User>(GetCurrentMethod(), cancellationToken, userId, request);

        // Invalidate cached user data
        Cache.Remove(HttpMethod.Get, $"users/{userId}", ComparisonOperator.Equal);
    }
}

Request Logging

Monitor API calls for debugging and analytics:

public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");

        // Enable request logging
        LogRequest += (method, uri, body) =>
        {
            Console.WriteLine($"[{DateTime.Now}] {method} {uri}");
            if (body != null)
                Console.WriteLine($"Body: {JsonConvert.SerializeObject(body)}");
        };
    }
}
Performance Tracing
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");

        // Enable performance tracing
        Tracing = true;
    }

    protected override void TraceCall(HttpMethod method, Uri uri, TimeSpan elapsed)
    {
        if (elapsed.TotalSeconds > 1)
            Console.WriteLine($"SLOW: {method} {uri} took {elapsed.TotalSeconds:F2}s");
    }
}

Advanced Features

Custom Formatters

Create custom formatters for specialized serialization:

public class XmlMediaTypeFormatter : MediaTypeFormatter
{
    public XmlMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
    }

    public override bool CanReadType(Type type) => true;
    public override bool CanWriteType(Type type) => true;

    public override async Task<object> ReadFromStreamAsync(
        Type type, Stream readStream, HttpContent content,
        IFormatterLogger formatterLogger, CancellationToken cancellationToken)
    {
        var serializer = new XmlSerializer(type);
        return serializer.Deserialize(readStream);
    }

    public override async Task WriteToStreamAsync(
        Type type, object value, Stream writeStream,
        HttpContent content, TransportContext transportContext,
        CancellationToken cancellationToken)
    {
        var serializer = new XmlSerializer(type);
        serializer.Serialize(writeStream, value);
    }
}

// Usage
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(
            new HttpClient(),
            new XmlMediaTypeFormatter(),
            new XmlMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");
    }
}

REST Attributes

Use attributes to customize endpoint names and parameter handling:

Custom Endpoint Names
using Ecng.Net;

public class MyApiClient : RestBaseApiClient
{
    // Method name: GetUserProfile
    // Without attribute: GET /getuserprofile?id=123
    // With attribute: GET /users/profile?id=123
    [Rest(Name = "users/profile")]
    public Task<UserProfile> GetUserProfile(int id, CancellationToken cancellationToken = default)
        => GetAsync<UserProfile>(GetCurrentMethod(), cancellationToken, id);
}
Custom Parameter Names
public class MyApiClient : RestBaseApiClient
{
    // Without attributes: GET /search?searchTerm=hello&pageSize=10
    // With attributes: GET /search?q=hello&limit=10
    public Task<SearchResults> Search(
        [Rest(Name = "q")] string searchTerm,
        [Rest(Name = "limit")] int pageSize,
        CancellationToken cancellationToken = default)
        => GetAsync<SearchResults>(GetCurrentMethod(), cancellationToken, searchTerm, pageSize);
}
Ignoring Parameters
public class MyApiClient : RestBaseApiClient
{
    // The 'options' parameter is used locally but not sent to the API
    public Task<User> GetUser(
        int userId,
        [Rest(Ignore = true)] RequestOptions options,
        CancellationToken cancellationToken = default)
    {
        // Use options for client-side logic
        if (options?.UseCache == true)
            Cache = new InMemoryRestApiClientCache(TimeSpan.FromMinutes(5));

        return GetAsync<User>(GetCurrentMethod(), cancellationToken, userId);
    }
}

Error Handling

Extracting Error Details
public class MyApiClient : RestBaseApiClient
{
    public MyApiClient()
        : base(new HttpClient(), new JsonMediaTypeFormatter(), new JsonMediaTypeFormatter())
    {
        BaseAddress = new Uri("https://api.example.com/");

        // Extract detailed error messages from API responses
        ExtractBadResponse = true;
    }
}

// If API returns: {"error": "Invalid credentials", "code": 401}
// Exception message will include: "401 (Unauthorized): {\"error\":\"Invalid credentials\",\"code\":401}"
Custom Response Validation
public class MyApiClient : RestBaseApiClient
{
    protected override async Task ValidateResponseAsync(
        HttpResponseMessage response,
        CancellationToken cancellationToken)
    {
        if (!response.IsSuccessStatusCode)
        {
            var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
            var error = JsonConvert.DeserializeObject<ApiError>(errorContent);

            throw new ApiException(error.Message, error.Code, response.StatusCode);
        }
    }
}
Custom Response Processing
public class MyApiClient : RestBaseApiClient
{
    protected override async Task<TResult> GetResultAsync<TResult>(
        HttpResponseMessage response,
        CancellationToken cancellationToken)
    {
        // Handle empty responses
        if (typeof(TResult) == typeof(VoidType))
            return default;

        var content = await response.Content.ReadAsStringAsync(cancellationToken);

        // Unwrap API envelope: {"success": true, "data": {...}}
        var envelope = JsonConvert.DeserializeObject<ApiEnvelope<TResult>>(content);
        return envelope.Data;
    }
}

Utilities

Sitemap Generation

Generate XML sitemaps for search engine optimization:

using Ecng.Net.Sitemap;
using System.Xml.Linq;

// Create sitemap nodes
var nodes = new List<SitemapNode>
{
    new SitemapNode("https://example.com/")
    {
        LastModified = DateTime.UtcNow,
        Frequency = SitemapFrequency.Daily,
        Priority = 1.0
    },
    new SitemapNode("https://example.com/products")
    {
        LastModified = DateTime.UtcNow.AddDays(-1),
        Frequency = SitemapFrequency.Weekly,
        Priority = 0.8
    },
    new SitemapNode("https://example.com/about")
    {
        Frequency = SitemapFrequency.Monthly,
        Priority = 0.5
    }
};

// Generate sitemap XML
XDocument sitemap = SitemapGenerator.GenerateSitemap(nodes);
sitemap.Save("sitemap.xml");
Multilingual Sitemaps
using Ecng.Net.Sitemap;

var node = new SitemapNode("https://example.com/products")
{
    LastModified = DateTime.UtcNow,
    Frequency = SitemapFrequency.Weekly,
    Priority = 0.8
};

// Add alternate language versions
node.AlternateLinks.Add(new XhtmlLink("https://example.com/en/products", "en"));
node.AlternateLinks.Add(new XhtmlLink("https://example.com/fr/products", "fr"));
node.AlternateLinks.Add(new XhtmlLink("https://example.com/de/products", "de"));
node.AlternateLinks.Add(new XhtmlLink("https://example.com/products", "x-default"));

var sitemap = SitemapGenerator.GenerateSitemap(new[] { node });
sitemap.Save("sitemap-multilingual.xml");
Sitemap Index

For large sites with multiple sitemaps:

using Ecng.Net.Sitemap;

var sitemapUrls = new[]
{
    "https://example.com/sitemap-products.xml",
    "https://example.com/sitemap-blog.xml",
    "https://example.com/sitemap-pages.xml"
};

XDocument sitemapIndex = SitemapGenerator.GenerateSitemapIndex(sitemapUrls);
sitemapIndex.Save("sitemap-index.xml");
Sitemap Frequency Options
public enum SitemapFrequency
{
    Never,    // Archived URLs that never change
    Yearly,   // Changes yearly
    Monthly,  // Changes monthly
    Weekly,   // Changes weekly
    Daily,    // Changes daily
    Hourly,   // Changes hourly
    Always    // Changes on every access
}

Captcha Validation

Interface for implementing captcha validation:

using Ecng.Net.Captcha;

public class RecaptchaValidator : ICaptchaValidator<RecaptchaResponse>
{
    private readonly string _secretKey;
    private readonly HttpClient _httpClient;

    public RecaptchaValidator(string secretKey)
    {
        _secretKey = secretKey;
        _httpClient = new HttpClient();
    }

    public async Task<RecaptchaResponse> ValidateAsync(
        string response,
        string address,
        CancellationToken cancellationToken = default)
    {
        var requestUri = "https://www.google.com/recaptcha/api/siteverify" +
            $"?secret={_secretKey}&response={response}&remoteip={address}";

        var httpResponse = await _httpClient.PostAsync(requestUri, null, cancellationToken);
        var content = await httpResponse.Content.ReadAsStringAsync(cancellationToken);

        return JsonConvert.DeserializeObject<RecaptchaResponse>(content);
    }
}

public class RecaptchaResponse
{
    public bool Success { get; set; }
    public DateTime ChallengeTs { get; set; }
    public string Hostname { get; set; }
    public string[] ErrorCodes { get; set; }
}

// Usage
var validator = new RecaptchaValidator("your-secret-key");
var result = await validator.ValidateAsync(captchaResponse, userIpAddress);

if (result.Success)
{
    // Captcha validated successfully
}

SMS Service

Interface for implementing SMS messaging:

using Ecng.Net.Sms;

public class TwilioSmsService : ISmsService
{
    private readonly string _accountSid;
    private readonly string _authToken;
    private readonly string _fromNumber;
    private readonly HttpClient _httpClient;

    public TwilioSmsService(string accountSid, string authToken, string fromNumber)
    {
        _accountSid = accountSid;
        _authToken = authToken;
        _fromNumber = fromNumber;
        _httpClient = new HttpClient();

        var credentials = Convert.ToBase64String(
            Encoding.ASCII.GetBytes($"{accountSid}:{authToken}"));
        _httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Basic", credentials);
    }

    public async Task<string> SendAsync(
        string phone,
        string message,
        CancellationToken cancellationToken = default)
    {
        var requestUri = $"https://api.twilio.com/2010-04-01/Accounts/{_accountSid}/Messages.json";

        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("From", _fromNumber),
            new KeyValuePair<string, string>("To", phone),
            new KeyValuePair<string, string>("Body", message)
        });

        var response = await _httpClient.PostAsync(requestUri, content, cancellationToken);
        return await response.Content.ReadAsStringAsync(cancellationToken);
    }
}

// Usage
var smsService = new TwilioSmsService("accountSid", "authToken", "+1234567890");
var result = await smsService.SendAsync("+1987654321", "Your verification code is: 123456");

Currency Converter

Interface for implementing currency conversion:

using Ecng.Net.Currencies;

public class ExchangeRateApiConverter : ICurrencyConverter
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;

    public ExchangeRateApiConverter(string apiKey)
    {
        _apiKey = apiKey;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://api.exchangerate-api.com/v4/")
        };
    }

    public async Task<decimal> GetRateAsync(
        CurrencyTypes from,
        CurrencyTypes to,
        DateTime date,
        CancellationToken cancellationToken = default)
    {
        var response = await _httpClient.GetAsync(
            $"latest/{from}?apikey={_apiKey}",
            cancellationToken);

        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        var data = JsonConvert.DeserializeObject<ExchangeRateResponse>(content);

        return data.Rates[to.ToString()];
    }
}

public class ExchangeRateResponse
{
    public Dictionary<string, decimal> Rates { get; set; }
}

// Usage
var converter = new ExchangeRateApiConverter("your-api-key");
var rate = await converter.GetRateAsync(
    CurrencyTypes.USD,
    CurrencyTypes.EUR,
    DateTime.UtcNow);

decimal amountInEur = 100m * rate;
Console.WriteLine($"$100 USD = €{amountInEur:F2} EUR");

API Reference

RestBaseApiClient

Base class for creating REST API clients.

Properties
Property Type Description
BaseAddress Uri The base URL for all API requests
Http HttpMessageInvoker The underlying HTTP client
RequestFormatter MediaTypeFormatter Serializer for request bodies
ResponseFormatter MediaTypeFormatter Deserializer for responses
PerRequestHeaders IDictionary<string, string> Headers added to every request
Cache IRestApiClientCache Response cache implementation
RetryPolicy RetryPolicyInfo Retry configuration
ExtractBadResponse bool Include error details in exceptions
Tracing bool Enable performance tracing
Events
Event Description
LogRequest Fired before each request is sent
Protected Methods
Method Description
GetAsync<TResult>() Execute HTTP GET request
PostAsync<TResult>() Execute HTTP POST request
PutAsync<TResult>() Execute HTTP PUT request
DeleteAsync<TResult>() Execute HTTP DELETE request
GetCurrentMethod() Get current method name for URL construction
AddAuthBearer() Add Bearer token authentication
AddAuth() Add custom authentication header

IRestApiClientCache

Interface for implementing response caching.

Methods
Method Description
TryGet<T>() Attempt to retrieve cached value
Set<T>() Store value in cache
Remove() Remove cached entries

InMemoryRestApiClientCache

In-memory cache implementation with expiration.

Constructor
public InMemoryRestApiClientCache(TimeSpan timeout)

RestAttribute

Attribute for customizing REST API behavior.

Properties
Property Type Description
Name string Custom name for endpoint or parameter
IsRequired bool Whether parameter is required
Ignore bool Exclude parameter from request

SitemapGenerator

Static class for generating XML sitemaps.

Methods
Method Description
GenerateSitemap() Create sitemap XML from nodes
GenerateSitemapIndex() Create sitemap index XML
CheckDocumentSize() Validate sitemap size (max 10MB)
CheckSitemapCount() Validate sitemap count (max 50,000)

SitemapNode

Represents a URL in a sitemap.

Properties
Property Type Description
Url string The URL (required)
LastModified DateTime? Last modification date
Frequency SitemapFrequency? Change frequency hint
Priority double? Priority (0.0 to 1.0)
AlternateLinks XhtmlLinkCollection Alternate language versions

Media Type Formatters

JsonMediaTypeFormatter

Standard JSON serialization using Newtonsoft.Json (from Microsoft.AspNet.WebApi.Client).

RestApiFormUrlEncodedMediaTypeFormatter

Form URL-encoded serialization for application/x-www-form-urlencoded content type.

TextMediaTypeFormatter

Plain text serialization/deserialization for text-based responses.

Best Practices

1. Use CancellationToken

Always support cancellation in your API methods:

public Task<User> GetUser(int id, CancellationToken cancellationToken = default)
    => GetAsync<User>(GetCurrentMethod(), cancellationToken, id);

2. Configure Appropriate Retry Policies

Set different retry counts for read vs. write operations:

public MyApiClient()
{
    RetryPolicy.ReadMaxCount = 5;   // More retries for reads
    RetryPolicy.WriteMaxCount = 1;  // Fewer retries for writes
}

3. Use Caching for Read-Heavy APIs

Enable caching for APIs with mostly GET requests:

public MyApiClient()
{
    Cache = new InMemoryRestApiClientCache(TimeSpan.FromMinutes(5));
}

4. Handle Errors Gracefully

Enable detailed error extraction for better debugging:

public MyApiClient()
{
    ExtractBadResponse = true;
}

5. Use Strongly-Typed DTOs

Define clear data transfer objects for requests and responses:

public class CreateUserRequest
{
    public string Username { get; set; }
    public string Email { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

public Task<User> CreateUser(CreateUserRequest request, CancellationToken cancellationToken = default)
    => PostAsync<User>(GetCurrentMethod(), cancellationToken, request);

License

This library is part of the StockSharp/Ecng project. Please refer to the project's license file for terms of use.

Contributing

Contributions are welcome! Please ensure that your code follows the existing patterns and includes appropriate documentation.

Support

For issues, questions, or feature requests, please use the StockSharp project's issue tracker.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 is compatible.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Ecng.Net.Clients:

Package Downloads
StockSharp.Mfd

MFD

StockSharp.Web.Api.Interfaces

StockSharp WebApi

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.315 35 1/22/2026
1.0.314 33 1/22/2026
1.0.313 37 1/21/2026
1.0.312 86 1/19/2026
1.0.311 88 1/19/2026
1.0.310 86 1/18/2026
1.0.309 84 1/18/2026
1.0.308 85 1/16/2026
1.0.307 90 1/14/2026
1.0.306 86 1/13/2026
1.0.305 85 1/13/2026
1.0.304 90 1/12/2026
1.0.303 88 1/9/2026
1.0.302 88 1/9/2026
1.0.301 94 1/8/2026
1.0.300 91 1/8/2026
1.0.299 88 1/7/2026
1.0.298 87 1/6/2026
1.0.297 89 1/6/2026
1.0.296 96 1/5/2026
1.0.295 88 1/4/2026
1.0.294 94 1/1/2026
1.0.293 96 12/31/2025
1.0.292 94 12/30/2025
1.0.291 94 12/30/2025
1.0.290 101 12/29/2025
1.0.289 103 12/29/2025
1.0.288 108 12/26/2025
1.0.287 99 12/26/2025
1.0.286 103 12/26/2025
1.0.285 119 12/26/2025
1.0.284 191 12/25/2025
1.0.283 186 12/25/2025
1.0.282 187 12/24/2025
1.0.281 185 12/23/2025
1.0.280 175 12/22/2025
1.0.279 157 12/21/2025
1.0.278 205 12/19/2025
1.0.277 236 12/19/2025
1.0.276 275 12/18/2025
1.0.275 268 12/17/2025
1.0.274 270 12/15/2025
1.0.273 262 12/15/2025
1.0.272 223 12/14/2025
1.0.271 164 12/14/2025
1.0.270 176 12/13/2025
1.0.269 176 12/13/2025
1.0.268 129 12/12/2025
1.0.267 124 12/12/2025
1.0.266 121 12/12/2025
1.0.265 120 12/12/2025
1.0.264 130 12/12/2025
1.0.263 134 12/12/2025
1.0.262 133 12/12/2025
1.0.261 680 12/2/2025
1.0.260 681 12/2/2025
1.0.259 687 12/2/2025
1.0.258 277 11/30/2025
1.0.257 144 11/29/2025
1.0.256 150 11/28/2025
1.0.255 142 11/28/2025
1.0.254 196 11/27/2025
1.0.253 206 11/24/2025
1.0.252 201 11/24/2025
1.0.251 198 11/23/2025
1.0.250 178 11/23/2025
1.0.249 212 11/22/2025
1.0.248 1,116 11/20/2025
1.0.247 414 11/18/2025
1.0.246 410 11/18/2025
1.0.245 304 11/13/2025
1.0.244 246 11/10/2025
1.0.243 160 11/1/2025
1.0.242 180 10/31/2025
1.0.241 211 10/28/2025
1.0.240 207 10/27/2025
1.0.239 205 10/27/2025
1.0.238 138 10/25/2025
1.0.237 145 10/24/2025
1.0.236 192 10/20/2025
1.0.235 206 10/12/2025
1.0.234 144 10/11/2025
1.0.233 198 10/7/2025
1.0.232 215 10/6/2025
1.0.231 171 10/3/2025
1.0.230 210 10/1/2025
1.0.229 206 10/1/2025
1.0.228 204 9/30/2025
1.0.227 190 9/28/2025
1.0.226 206 9/25/2025
1.0.225 895 9/5/2025
1.0.224 195 9/2/2025
1.0.223 3,455 8/30/2025
1.0.222 366 8/30/2025
1.0.221 328 8/20/2025
1.0.220 194 8/20/2025
1.0.219 199 8/19/2025
1.0.218 180 8/15/2025
1.0.217 214 8/10/2025
1.0.216 457 7/16/2025
1.0.215 206 7/14/2025
1.0.214 221 7/13/2025
1.0.213 211 7/13/2025
1.0.212 169 7/12/2025
1.0.211 284 7/8/2025
1.0.210 187 7/4/2025
1.0.209 219 7/2/2025
1.0.208 251 6/24/2025
1.0.207 2,487 6/16/2025
1.0.206 375 6/9/2025
1.0.205 269 6/8/2025
1.0.204 329 5/21/2025
1.0.203 237 5/21/2025
1.0.202 219 5/17/2025
1.0.201 439 5/12/2025
1.0.200 293 5/12/2025
1.0.199 268 5/12/2025
1.0.198 213 5/11/2025
1.0.197 194 5/11/2025
1.0.196 149 5/10/2025
1.0.195 146 5/10/2025
1.0.194 241 5/6/2025
1.0.193 153 5/3/2025
1.0.192 280 4/17/2025
1.0.191 265 4/15/2025
1.0.190 189 4/12/2025
1.0.189 262 4/9/2025
1.0.188 1,985 4/6/2025
1.0.187 178 4/5/2025
1.0.186 311 3/22/2025
1.0.185 273 3/20/2025
1.0.184 229 3/20/2025
1.0.183 243 3/19/2025
1.0.182 540 2/26/2025
1.0.181 193 2/26/2025
1.0.180 444 2/8/2025
1.0.179 194 2/8/2025
1.0.178 205 2/8/2025
1.0.177 194 2/6/2025
1.0.176 194 2/6/2025
1.0.175 189 2/6/2025
1.0.174 206 2/6/2025
1.0.173 182 2/6/2025
1.0.172 2,064 2/5/2025
1.0.171 202 2/5/2025
1.0.170 209 2/5/2025
1.0.169 511 2/3/2025
1.0.168 220 2/2/2025
1.0.167 214 2/1/2025
1.0.166 202 1/31/2025
1.0.165 211 1/30/2025
1.0.164 183 1/26/2025
1.0.163 221 1/21/2025
1.0.162 184 1/20/2025
1.0.161 183 1/20/2025
1.0.160 186 1/19/2025
1.0.159 190 1/19/2025
1.0.158 409 1/15/2025
1.0.157 318 1/15/2025
1.0.156 319 1/15/2025
1.0.155 188 1/14/2025
1.0.154 190 1/12/2025
1.0.153 170 1/12/2025
1.0.152 222 1/12/2025
1.0.151 181 1/12/2025
1.0.150 195 1/10/2025
1.0.149 619 12/30/2024
1.0.148 197 12/27/2024
1.0.147 223 12/19/2024
1.0.146 220 11/20/2024
1.0.145 197 11/19/2024
1.0.144 206 11/19/2024
1.0.143 1,912 11/18/2024
1.0.142 235 11/18/2024
1.0.141 303 11/7/2024
1.0.140 189 10/31/2024
1.0.139 221 10/19/2024
1.0.138 259 10/19/2024
1.0.137 241 10/19/2024
1.0.136 1,177 10/13/2024
1.0.135 236 10/12/2024
1.0.134 311 10/9/2024
1.0.133 213 10/9/2024
1.0.132 258 10/5/2024
1.0.131 1,467 9/18/2024
1.0.130 223 9/18/2024
1.0.129 406 9/18/2024
1.0.128 216 9/17/2024
1.0.127 233 9/17/2024
1.0.126 415 9/3/2024
1.0.125 203 9/1/2024
1.0.124 571 8/8/2024
1.0.123 2,200 7/25/2024
1.0.122 197 7/23/2024
1.0.121 498 7/23/2024
1.0.120 337 7/23/2024
1.0.119 830 7/4/2024
1.0.118 1,325 6/12/2024
1.0.117 402 6/12/2024
1.0.116 211 6/12/2024
1.0.115 644 5/28/2024
1.0.114 464 5/4/2024
1.0.113 347 4/23/2024
1.0.112 2,105 4/21/2024
1.0.111 384 4/14/2024
1.0.110 875 4/5/2024
1.0.109 669 3/28/2024
1.0.108 246 3/17/2024
1.0.107 983 3/9/2024
1.0.106 387 2/23/2024
1.0.105 240 2/23/2024
1.0.104 761 2/18/2024
1.0.103 198 2/18/2024
1.0.102 206 2/16/2024
1.0.101 334 2/13/2024
1.0.100 309 2/8/2024
1.0.99 303 2/5/2024
1.0.98 212 2/4/2024
1.0.97 317 1/23/2024
1.0.96 224 1/23/2024
1.0.95 1,664 1/12/2024
1.0.94 1,800 1/2/2024
1.0.93 403 12/29/2023
1.0.92 571 12/17/2023
1.0.91 478 12/15/2023
1.0.90 218 12/15/2023
1.0.89 244 12/15/2023
1.0.88 935 12/13/2023
1.0.87 261 12/13/2023
1.0.86 247 12/10/2023
1.0.85 2,125 11/18/2023
1.0.84 220 11/18/2023
1.0.83 210 11/18/2023
1.0.82 187 11/17/2023
1.0.81 196 11/12/2023
1.0.80 193 11/12/2023
1.0.79 210 11/10/2023
1.0.78 192 11/10/2023
1.0.77 398 11/9/2023
1.0.76 204 11/9/2023
1.0.75 192 11/9/2023
1.0.74 581 11/3/2023
1.0.73 185 11/1/2023
1.0.72 220 11/1/2023
1.0.71 2,540 9/8/2023
1.0.70 432 9/8/2023
1.0.69 387 9/3/2023
1.0.68 246 8/27/2023
1.0.67 230 8/24/2023
1.0.66 281 8/21/2023
1.0.65 670 8/15/2023
1.0.64 260 8/14/2023
1.0.63 230 8/14/2023
1.0.62 862 8/10/2023
1.0.61 1,219 7/29/2023
1.0.60 1,574 7/1/2023
1.0.59 306 6/29/2023
1.0.58 2,184 6/4/2023
1.0.57 979 5/27/2023
1.0.56 598 5/21/2023
1.0.55 735 5/19/2023
1.0.54 2,715 5/8/2023
1.0.53 310 5/7/2023
1.0.52 344 5/6/2023
1.0.51 908 5/5/2023
1.0.50 284 5/5/2023
1.0.49 1,596 5/1/2023
1.0.48 1,822 4/22/2023
1.0.47 320 4/21/2023
1.0.46 324 4/21/2023
1.0.45 1,714 4/13/2023
1.0.44 766 4/3/2023
1.0.43 2,180 3/27/2023
1.0.42 1,546 3/21/2023
1.0.41 857 3/18/2023
1.0.40 396 3/18/2023
1.0.39 413 3/17/2023
1.0.38 810 3/13/2023
1.0.37 942 3/6/2023
1.0.36 1,413 2/26/2023
1.0.35 1,424 2/21/2023
1.0.34 412 2/20/2023
1.0.33 1,358 2/16/2023
1.0.32 442 2/15/2023
1.0.31 397 2/14/2023
1.0.30 399 2/14/2023
1.0.29 916 2/9/2023
1.0.28 2,155 2/7/2023
1.0.27 435 2/4/2023
1.0.26 448 2/4/2023
1.0.25 473 2/3/2023
1.0.24 447 2/3/2023
1.0.23 425 2/2/2023
1.0.22 450 1/30/2023
1.0.21 1,899 1/30/2023
1.0.20 2,544 1/25/2023
1.0.19 459 1/23/2023
1.0.18 454 1/23/2023
1.0.17 494 1/18/2023
1.0.16 477 1/15/2023
1.0.15 455 1/6/2023
1.0.14 487 1/1/2023
1.0.13 448 12/31/2022
1.0.12 448 12/30/2022
1.0.11 460 12/29/2022
1.0.10 460 12/23/2022
1.0.9 465 12/12/2022
1.0.8 446 12/8/2022
1.0.7 493 12/4/2022
1.0.6 481 12/4/2022
1.0.5 468 12/2/2022
1.0.4 486 11/30/2022
1.0.3 477 11/29/2022
1.0.2 514 11/28/2022
1.0.1 503 11/26/2022
1.0.0 478 11/26/2022