Ecng.Net.Clients 1.0.304

dotnet add package Ecng.Net.Clients --version 1.0.304
                    
NuGet\Install-Package Ecng.Net.Clients -Version 1.0.304
                    
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.304" />
                    
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.304" />
                    
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.304
                    
#r "nuget: Ecng.Net.Clients, 1.0.304"
                    
#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.304
                    
#: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.304
                    
Install as a Cake Addin
#tool nuget:?package=Ecng.Net.Clients&version=1.0.304
                    
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.304 2 1/12/2026
1.0.303 36 1/9/2026
1.0.302 35 1/9/2026
1.0.301 41 1/8/2026
1.0.300 39 1/8/2026
1.0.299 37 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 92 12/31/2025
1.0.292 91 12/30/2025
1.0.291 90 12/30/2025
1.0.290 97 12/29/2025
1.0.289 99 12/29/2025
1.0.288 103 12/26/2025
1.0.287 95 12/26/2025
1.0.286 100 12/26/2025
1.0.285 113 12/26/2025
1.0.284 187 12/25/2025
1.0.283 182 12/25/2025
1.0.282 184 12/24/2025
1.0.281 183 12/23/2025
1.0.280 171 12/22/2025
1.0.279 154 12/21/2025
1.0.278 202 12/19/2025
1.0.277 234 12/19/2025
1.0.276 271 12/18/2025
1.0.275 266 12/17/2025
1.0.274 263 12/15/2025
1.0.273 255 12/15/2025
1.0.272 219 12/14/2025
1.0.271 158 12/14/2025
1.0.270 171 12/13/2025
1.0.269 169 12/13/2025
1.0.268 124 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 130 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 269 11/30/2025
1.0.257 141 11/29/2025
1.0.256 144 11/28/2025
1.0.255 139 11/28/2025
1.0.254 192 11/27/2025
1.0.253 202 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 207 11/22/2025
1.0.248 1,047 11/20/2025
1.0.247 411 11/18/2025
1.0.246 404 11/18/2025
1.0.245 299 11/13/2025
1.0.244 242 11/10/2025
1.0.243 156 11/1/2025
1.0.242 177 10/31/2025
1.0.241 206 10/28/2025
1.0.240 203 10/27/2025
1.0.239 200 10/27/2025
1.0.238 129 10/25/2025
1.0.237 138 10/24/2025
1.0.236 189 10/20/2025
1.0.235 201 10/12/2025
1.0.234 138 10/11/2025
1.0.233 195 10/7/2025
1.0.232 206 10/6/2025
1.0.231 165 10/3/2025
1.0.230 203 10/1/2025
1.0.229 201 10/1/2025
1.0.228 199 9/30/2025
1.0.227 185 9/28/2025
1.0.226 201 9/25/2025
1.0.225 849 9/5/2025
1.0.224 191 9/2/2025
1.0.223 3,372 8/30/2025
1.0.222 360 8/30/2025
1.0.221 319 8/20/2025
1.0.220 189 8/20/2025
1.0.219 196 8/19/2025
1.0.218 173 8/15/2025
1.0.217 210 8/10/2025
1.0.216 449 7/16/2025
1.0.215 200 7/14/2025
1.0.214 214 7/13/2025
1.0.213 207 7/13/2025
1.0.212 164 7/12/2025
1.0.211 278 7/8/2025
1.0.210 182 7/4/2025
1.0.209 214 7/2/2025
1.0.208 246 6/24/2025
1.0.207 2,482 6/16/2025
1.0.206 369 6/9/2025
1.0.205 266 6/8/2025
1.0.204 322 5/21/2025
1.0.203 228 5/21/2025
1.0.202 214 5/17/2025
1.0.201 433 5/12/2025
1.0.200 286 5/12/2025
1.0.199 264 5/12/2025
1.0.198 209 5/11/2025
1.0.197 190 5/11/2025
1.0.196 140 5/10/2025
1.0.195 140 5/10/2025
1.0.194 234 5/6/2025
1.0.193 149 5/3/2025
1.0.192 275 4/17/2025
1.0.191 257 4/15/2025
1.0.190 179 4/12/2025
1.0.189 256 4/9/2025
1.0.188 1,980 4/6/2025
1.0.187 172 4/5/2025
1.0.186 305 3/22/2025
1.0.185 266 3/20/2025
1.0.184 225 3/20/2025
1.0.183 237 3/19/2025
1.0.182 538 2/26/2025
1.0.181 188 2/26/2025
1.0.180 438 2/8/2025
1.0.179 189 2/8/2025
1.0.178 200 2/8/2025
1.0.177 187 2/6/2025
1.0.176 188 2/6/2025
1.0.175 186 2/6/2025
1.0.174 200 2/6/2025
1.0.173 179 2/6/2025
1.0.172 2,057 2/5/2025
1.0.171 195 2/5/2025
1.0.170 205 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 207 1/30/2025
1.0.164 179 1/26/2025
1.0.163 216 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 187 1/19/2025
1.0.158 404 1/15/2025
1.0.157 314 1/15/2025
1.0.156 314 1/15/2025
1.0.155 181 1/14/2025
1.0.154 186 1/12/2025
1.0.153 164 1/12/2025
1.0.152 219 1/12/2025
1.0.151 179 1/12/2025
1.0.150 190 1/10/2025
1.0.149 615 12/30/2024
1.0.148 195 12/27/2024
1.0.147 215 12/19/2024
1.0.146 216 11/20/2024
1.0.145 193 11/19/2024
1.0.144 203 11/19/2024
1.0.143 1,908 11/18/2024
1.0.142 230 11/18/2024
1.0.141 299 11/7/2024
1.0.140 186 10/31/2024
1.0.139 215 10/19/2024
1.0.138 254 10/19/2024
1.0.137 236 10/19/2024
1.0.136 1,173 10/13/2024
1.0.135 230 10/12/2024
1.0.134 305 10/9/2024
1.0.133 207 10/9/2024
1.0.132 254 10/5/2024
1.0.131 1,462 9/18/2024
1.0.130 219 9/18/2024
1.0.129 403 9/18/2024
1.0.128 212 9/17/2024
1.0.127 230 9/17/2024
1.0.126 408 9/3/2024
1.0.125 196 9/1/2024
1.0.124 567 8/8/2024
1.0.123 2,193 7/25/2024
1.0.122 190 7/23/2024
1.0.121 494 7/23/2024
1.0.120 333 7/23/2024
1.0.119 827 7/4/2024
1.0.118 1,319 6/12/2024
1.0.117 397 6/12/2024
1.0.116 204 6/12/2024
1.0.115 640 5/28/2024
1.0.114 456 5/4/2024
1.0.113 341 4/23/2024
1.0.112 2,098 4/21/2024
1.0.111 378 4/14/2024
1.0.110 868 4/5/2024
1.0.109 664 3/28/2024
1.0.108 243 3/17/2024
1.0.107 979 3/9/2024
1.0.106 383 2/23/2024
1.0.105 234 2/23/2024
1.0.104 754 2/18/2024
1.0.103 195 2/18/2024
1.0.102 204 2/16/2024
1.0.101 330 2/13/2024
1.0.100 304 2/8/2024
1.0.99 299 2/5/2024
1.0.98 207 2/4/2024
1.0.97 309 1/23/2024
1.0.96 218 1/23/2024
1.0.95 1,661 1/12/2024
1.0.94 1,796 1/2/2024
1.0.93 395 12/29/2023
1.0.92 567 12/17/2023
1.0.91 474 12/15/2023
1.0.90 214 12/15/2023
1.0.89 242 12/15/2023
1.0.88 924 12/13/2023
1.0.87 256 12/13/2023
1.0.86 244 12/10/2023
1.0.85 2,122 11/18/2023
1.0.84 219 11/18/2023
1.0.83 205 11/18/2023
1.0.82 184 11/17/2023
1.0.81 192 11/12/2023
1.0.80 191 11/12/2023
1.0.79 207 11/10/2023
1.0.78 187 11/10/2023
1.0.77 397 11/9/2023
1.0.76 202 11/9/2023
1.0.75 189 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 428 9/8/2023
1.0.69 385 9/3/2023
1.0.68 243 8/27/2023
1.0.67 227 8/24/2023
1.0.66 277 8/21/2023
1.0.65 665 8/15/2023
1.0.64 257 8/14/2023
1.0.63 225 8/14/2023
1.0.62 859 8/10/2023
1.0.61 1,215 7/29/2023
1.0.60 1,570 7/1/2023
1.0.59 305 6/29/2023
1.0.58 2,180 6/4/2023
1.0.57 977 5/27/2023
1.0.56 593 5/21/2023
1.0.55 731 5/19/2023
1.0.54 2,712 5/8/2023
1.0.53 308 5/7/2023
1.0.52 342 5/6/2023
1.0.51 905 5/5/2023
1.0.50 282 5/5/2023
1.0.49 1,593 5/1/2023
1.0.48 1,819 4/22/2023
1.0.47 318 4/21/2023
1.0.46 323 4/21/2023
1.0.45 1,712 4/13/2023
1.0.44 765 4/3/2023
1.0.43 2,178 3/27/2023
1.0.42 1,542 3/21/2023
1.0.41 850 3/18/2023
1.0.40 392 3/18/2023
1.0.39 408 3/17/2023
1.0.38 808 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,356 2/16/2023
1.0.32 441 2/15/2023
1.0.31 394 2/14/2023
1.0.30 394 2/14/2023
1.0.29 914 2/9/2023
1.0.28 2,153 2/7/2023
1.0.27 433 2/4/2023
1.0.26 437 2/4/2023
1.0.25 470 2/3/2023
1.0.24 443 2/3/2023
1.0.23 423 2/2/2023
1.0.22 446 1/30/2023
1.0.21 1,899 1/30/2023
1.0.20 2,543 1/25/2023
1.0.19 453 1/23/2023
1.0.18 452 1/23/2023
1.0.17 492 1/18/2023
1.0.16 474 1/15/2023
1.0.15 453 1/6/2023
1.0.14 485 1/1/2023
1.0.13 445 12/31/2022
1.0.12 447 12/30/2022
1.0.11 459 12/29/2022
1.0.10 455 12/23/2022
1.0.9 463 12/12/2022
1.0.8 445 12/8/2022
1.0.7 489 12/4/2022
1.0.6 478 12/4/2022
1.0.5 463 12/2/2022
1.0.4 482 11/30/2022
1.0.3 471 11/29/2022
1.0.2 510 11/28/2022
1.0.1 499 11/26/2022
1.0.0 472 11/26/2022