Ecng.Net.Clients 1.0.303

dotnet add package Ecng.Net.Clients --version 1.0.303
                    
NuGet\Install-Package Ecng.Net.Clients -Version 1.0.303
                    
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.303" />
                    
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.303" />
                    
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.303
                    
#r "nuget: Ecng.Net.Clients, 1.0.303"
                    
#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.303
                    
#: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.303
                    
Install as a Cake Addin
#tool nuget:?package=Ecng.Net.Clients&version=1.0.303
                    
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.303 0 1/9/2026
1.0.302 26 1/9/2026
1.0.301 38 1/8/2026
1.0.300 38 1/8/2026
1.0.299 36 1/7/2026
1.0.298 37 1/6/2026
1.0.297 39 1/6/2026
1.0.296 81 1/5/2026
1.0.295 82 1/4/2026
1.0.294 90 1/1/2026
1.0.293 91 12/31/2025
1.0.292 89 12/30/2025
1.0.291 89 12/30/2025
1.0.290 97 12/29/2025
1.0.289 96 12/29/2025
1.0.288 102 12/26/2025
1.0.287 94 12/26/2025
1.0.286 99 12/26/2025
1.0.285 110 12/26/2025
1.0.284 185 12/25/2025
1.0.283 180 12/25/2025
1.0.282 184 12/24/2025
1.0.281 180 12/23/2025
1.0.280 171 12/22/2025
1.0.279 154 12/21/2025
1.0.278 200 12/19/2025
1.0.277 234 12/19/2025
1.0.276 270 12/18/2025
1.0.275 265 12/17/2025
1.0.274 261 12/15/2025
1.0.273 255 12/15/2025
1.0.272 219 12/14/2025
1.0.271 157 12/14/2025
1.0.270 168 12/13/2025
1.0.269 169 12/13/2025
1.0.268 122 12/12/2025
1.0.267 116 12/12/2025
1.0.266 118 12/12/2025
1.0.265 117 12/12/2025
1.0.264 121 12/12/2025
1.0.263 132 12/12/2025
1.0.262 128 12/12/2025
1.0.261 676 12/2/2025
1.0.260 674 12/2/2025
1.0.259 685 12/2/2025
1.0.258 268 11/30/2025
1.0.257 138 11/29/2025
1.0.256 144 11/28/2025
1.0.255 138 11/28/2025
1.0.254 192 11/27/2025
1.0.253 200 11/24/2025
1.0.252 195 11/24/2025
1.0.251 196 11/23/2025
1.0.250 175 11/23/2025
1.0.249 206 11/22/2025
1.0.248 1,011 11/20/2025
1.0.247 409 11/18/2025
1.0.246 402 11/18/2025
1.0.245 299 11/13/2025
1.0.244 240 11/10/2025
1.0.243 156 11/1/2025
1.0.242 177 10/31/2025
1.0.241 205 10/28/2025
1.0.240 203 10/27/2025
1.0.239 200 10/27/2025
1.0.238 127 10/25/2025
1.0.237 138 10/24/2025
1.0.236 189 10/20/2025
1.0.235 199 10/12/2025
1.0.234 135 10/11/2025
1.0.233 192 10/7/2025
1.0.232 204 10/6/2025
1.0.231 162 10/3/2025
1.0.230 198 10/1/2025
1.0.229 197 10/1/2025
1.0.228 199 9/30/2025
1.0.227 182 9/28/2025
1.0.226 201 9/25/2025
1.0.225 837 9/5/2025
1.0.224 191 9/2/2025
1.0.223 3,334 8/30/2025
1.0.222 360 8/30/2025
1.0.221 318 8/20/2025
1.0.220 187 8/20/2025
1.0.219 196 8/19/2025
1.0.218 173 8/15/2025
1.0.217 208 8/10/2025
1.0.216 446 7/16/2025
1.0.215 198 7/14/2025
1.0.214 213 7/13/2025
1.0.213 204 7/13/2025
1.0.212 162 7/12/2025
1.0.211 276 7/8/2025
1.0.210 180 7/4/2025
1.0.209 210 7/2/2025
1.0.208 246 6/24/2025
1.0.207 2,478 6/16/2025
1.0.206 367 6/9/2025
1.0.205 263 6/8/2025
1.0.204 317 5/21/2025
1.0.203 227 5/21/2025
1.0.202 213 5/17/2025
1.0.201 431 5/12/2025
1.0.200 284 5/12/2025
1.0.199 261 5/12/2025
1.0.198 208 5/11/2025
1.0.197 187 5/11/2025
1.0.196 139 5/10/2025
1.0.195 140 5/10/2025
1.0.194 233 5/6/2025
1.0.193 149 5/3/2025
1.0.192 273 4/17/2025
1.0.191 256 4/15/2025
1.0.190 177 4/12/2025
1.0.189 256 4/9/2025
1.0.188 1,977 4/6/2025
1.0.187 170 4/5/2025
1.0.186 304 3/22/2025
1.0.185 263 3/20/2025
1.0.184 222 3/20/2025
1.0.183 236 3/19/2025
1.0.182 537 2/26/2025
1.0.181 185 2/26/2025
1.0.180 437 2/8/2025
1.0.179 187 2/8/2025
1.0.178 198 2/8/2025
1.0.177 185 2/6/2025
1.0.176 187 2/6/2025
1.0.175 185 2/6/2025
1.0.174 200 2/6/2025
1.0.173 178 2/6/2025
1.0.172 2,054 2/5/2025
1.0.171 193 2/5/2025
1.0.170 202 2/5/2025
1.0.169 507 2/3/2025
1.0.168 217 2/2/2025
1.0.167 209 2/1/2025
1.0.166 196 1/31/2025
1.0.165 205 1/30/2025
1.0.164 176 1/26/2025
1.0.163 215 1/21/2025
1.0.162 180 1/20/2025
1.0.161 180 1/20/2025
1.0.160 181 1/19/2025
1.0.159 186 1/19/2025
1.0.158 402 1/15/2025
1.0.157 314 1/15/2025
1.0.156 312 1/15/2025
1.0.155 180 1/14/2025
1.0.154 185 1/12/2025
1.0.153 164 1/12/2025
1.0.152 217 1/12/2025
1.0.151 176 1/12/2025
1.0.150 189 1/10/2025
1.0.149 614 12/30/2024
1.0.148 193 12/27/2024
1.0.147 213 12/19/2024
1.0.146 216 11/20/2024
1.0.145 192 11/19/2024
1.0.144 200 11/19/2024
1.0.143 1,907 11/18/2024
1.0.142 229 11/18/2024
1.0.141 298 11/7/2024
1.0.140 183 10/31/2024
1.0.139 214 10/19/2024
1.0.138 252 10/19/2024
1.0.137 235 10/19/2024
1.0.136 1,171 10/13/2024
1.0.135 229 10/12/2024
1.0.134 304 10/9/2024
1.0.133 203 10/9/2024
1.0.132 254 10/5/2024
1.0.131 1,461 9/18/2024
1.0.130 215 9/18/2024
1.0.129 402 9/18/2024
1.0.128 211 9/17/2024
1.0.127 230 9/17/2024
1.0.126 407 9/3/2024
1.0.125 194 9/1/2024
1.0.124 566 8/8/2024
1.0.123 2,193 7/25/2024
1.0.122 190 7/23/2024
1.0.121 493 7/23/2024
1.0.120 332 7/23/2024
1.0.119 825 7/4/2024
1.0.118 1,316 6/12/2024
1.0.117 394 6/12/2024
1.0.116 204 6/12/2024
1.0.115 637 5/28/2024
1.0.114 452 5/4/2024
1.0.113 338 4/23/2024
1.0.112 2,096 4/21/2024
1.0.111 376 4/14/2024
1.0.110 868 4/5/2024
1.0.109 664 3/28/2024
1.0.108 241 3/17/2024
1.0.107 979 3/9/2024
1.0.106 382 2/23/2024
1.0.105 234 2/23/2024
1.0.104 754 2/18/2024
1.0.103 193 2/18/2024
1.0.102 202 2/16/2024
1.0.101 329 2/13/2024
1.0.100 302 2/8/2024
1.0.99 298 2/5/2024
1.0.98 207 2/4/2024
1.0.97 306 1/23/2024
1.0.96 216 1/23/2024
1.0.95 1,659 1/12/2024
1.0.94 1,792 1/2/2024
1.0.93 394 12/29/2023
1.0.92 565 12/17/2023
1.0.91 474 12/15/2023
1.0.90 214 12/15/2023
1.0.89 241 12/15/2023
1.0.88 921 12/13/2023
1.0.87 254 12/13/2023
1.0.86 244 12/10/2023
1.0.85 2,121 11/18/2023
1.0.84 216 11/18/2023
1.0.83 204 11/18/2023
1.0.82 184 11/17/2023
1.0.81 191 11/12/2023
1.0.80 189 11/12/2023
1.0.79 205 11/10/2023
1.0.78 185 11/10/2023
1.0.77 395 11/9/2023
1.0.76 202 11/9/2023
1.0.75 185 11/9/2023
1.0.74 575 11/3/2023
1.0.73 179 11/1/2023
1.0.72 215 11/1/2023
1.0.71 2,536 9/8/2023
1.0.70 427 9/8/2023
1.0.69 384 9/3/2023
1.0.68 243 8/27/2023
1.0.67 227 8/24/2023
1.0.66 276 8/21/2023
1.0.65 665 8/15/2023
1.0.64 254 8/14/2023
1.0.63 225 8/14/2023
1.0.62 858 8/10/2023
1.0.61 1,213 7/29/2023
1.0.60 1,570 7/1/2023
1.0.59 303 6/29/2023
1.0.58 2,176 6/4/2023
1.0.57 974 5/27/2023
1.0.56 591 5/21/2023
1.0.55 729 5/19/2023
1.0.54 2,712 5/8/2023
1.0.53 307 5/7/2023
1.0.52 342 5/6/2023
1.0.51 903 5/5/2023
1.0.50 281 5/5/2023
1.0.49 1,593 5/1/2023
1.0.48 1,818 4/22/2023
1.0.47 316 4/21/2023
1.0.46 323 4/21/2023
1.0.45 1,709 4/13/2023
1.0.44 764 4/3/2023
1.0.43 2,177 3/27/2023
1.0.42 1,538 3/21/2023
1.0.41 850 3/18/2023
1.0.40 389 3/18/2023
1.0.39 406 3/17/2023
1.0.38 805 3/13/2023
1.0.37 939 3/6/2023
1.0.36 1,412 2/26/2023
1.0.35 1,422 2/21/2023
1.0.34 410 2/20/2023
1.0.33 1,355 2/16/2023
1.0.32 441 2/15/2023
1.0.31 392 2/14/2023
1.0.30 393 2/14/2023
1.0.29 911 2/9/2023
1.0.28 2,153 2/7/2023
1.0.27 432 2/4/2023
1.0.26 436 2/4/2023
1.0.25 468 2/3/2023
1.0.24 441 2/3/2023
1.0.23 423 2/2/2023
1.0.22 444 1/30/2023
1.0.21 1,898 1/30/2023
1.0.20 2,540 1/25/2023
1.0.19 452 1/23/2023
1.0.18 451 1/23/2023
1.0.17 491 1/18/2023
1.0.16 472 1/15/2023
1.0.15 453 1/6/2023
1.0.14 482 1/1/2023
1.0.13 444 12/31/2022
1.0.12 447 12/30/2022
1.0.11 456 12/29/2022
1.0.10 455 12/23/2022
1.0.9 460 12/12/2022
1.0.8 443 12/8/2022
1.0.7 487 12/4/2022
1.0.6 476 12/4/2022
1.0.5 463 12/2/2022
1.0.4 482 11/30/2022
1.0.3 469 11/29/2022
1.0.2 509 11/28/2022
1.0.1 499 11/26/2022
1.0.0 471 11/26/2022