Linkit
SDK Reference

C# / .NET Framework 4.8 (Legacy)

Enterprise-grade C# SDK for the Linkit API — .NET Framework 4.8, Newtonsoft.Json, task-based async, fluent builders, NuGet package.

C# / .NET SDK (Legacy)

Package

sa.linkit.LinkitDotNetLegacy · .NET Framework 4.8 · Newtonsoft.Json · Enterprise-ready

Greenfield Projects? Use the Modern SDK

Starting a new project on .NET 10+? See the C# / .NET Modern SDK — zero external dependencies, IAsyncEnumerable streaming, and System.Text.Json source generation for maximum performance.


When to Use This SDK

Choose LinkitDotNetLegacy when:

  • Your project targets .NET Framework 4.8 and cannot migrate to .NET 10+
  • You need Newtonsoft.Json compatibility for existing serialization pipelines
  • You operate in environments that require .NET Framework runtime (Windows Server legacy, WCF services, etc.)
  • Your CI/CD builds against MSBuild Classic tooling

Modern vs Legacy at a Glance

FeatureLinkitDotNet (Modern)LinkitDotNetLegacy
Target.NET 10+.NET Framework 4.8
JSONSystem.Text.Json source genNewtonsoft.Json cached settings
StreamingIAsyncEnumerableTask-based batch processing
External DepsZeroNewtonsoft.Json, polyfills
NuGet PackageLinkitDotNetLinkitDotNetLegacy
ConfigureAwaitNot neededRequired (ConfigureAwait(false))
Allocations3 per create op5 per create op
NamespaceLinkitDotNet.*LinkitDotNetLegacy.*
Disposalawait usingusing

Installation

nuget install LinkitDotNetLegacy
dotnet add package LinkitDotNetLegacy
<PackageReference Include="LinkitDotNetLegacy" Version="0.0.4" />

Dependencies

The Legacy SDK ships with these pinned dependencies:

PackageVersionPurpose
Newtonsoft.Json13.0.3JSON serialization
System.Net.Http4.3.4HTTP transport
System.Threading.Tasks.Extensions4.5.4ValueTask support
System.Memory4.5.5Span<T> polyfills
Microsoft.Bcl.AsyncInterfaces8.0.0Async interface polyfills
System.ComponentModel.Annotations5.0.0Validation attributes

Quick Start

Initialize the Client

using LinkitDotNetLegacy.Client;
using LinkitDotNetLegacy.Configuration;

// Note: uses 'using' (not 'await using') on .NET Framework
using var client = await LinkitClient.CreateClientAsync(
    config => config
        .WithBaseUrl("https://linkit.works/api/v1")
        .WithTimeout(TimeSpan.FromSeconds(30))
        .WithRetryPolicy(retry => retry.MaxRetries = 3),
    jwtToken: "your-jwt-token"
);

Create a Product

var product = await client.Products()
    .Create()
    .WithId("PROD-001")
    .WithName("Premium Widget", "ويدجت متميز")
    .WithPrice(99.99, vatPercentage: 0.15)
    .WithBarcode("1234567890123")
    .EnableQuickCommerce()
    .AsEnabled()
    .ExecuteAsync();

Update SKU Inventory

// SKUs are auto-generated — never create manually
await client.Skus()
    .UpdateStock("AUTO-SKU-ID", "BR-RIYADH-001")
    .SetQuantity(500)
    .SetAvailability(true)
    .ExecuteAsync();

Process Large Datasets (Legacy Pattern)

// Task-based batch processing (Legacy alternative to IAsyncEnumerable)
await client.Products()
    .ProcessInBatches()
    .Where(p => p.AveragePrice > 1000)
    .WithBatchSize(100)
    .WithConcurrency(10)
    .Process(async products => {
        foreach (var product in products)
            await ProcessProductAsync(product);
    })
    .ProcessAllAsync();

Modern Alternative

The Modern SDK uses IAsyncEnumerable via .Stream() for memory-efficient, per-item streaming. The Legacy SDK uses .ProcessInBatches() with configurable batch sizes.


Configuration

using var client = await LinkitClient.CreateClientAsync(
    config => config
        .WithBaseUrl("https://linkit.works/api/v1")
        .WithTimeout(TimeSpan.FromMinutes(2))
        .WithMaxConcurrentRequests(100)
        .WithRetryPolicy(retry => {
            retry.MaxRetries = 5;
            retry.InitialDelay = TimeSpan.FromMilliseconds(200);
            retry.BackoffMultiplier = 2.0;
            retry.MaxDelay = TimeSpan.FromSeconds(30);
        })
        .WithDefaultHeader("X-Client-Version", "1.0.0")
        .WithDefaultHeader("X-Correlation-Id", Guid.NewGuid().ToString())
        .WithRequestLogging(
            onBeforeRequest: req => logger.LogTrace("[Outbound] {Method} {Uri}", req.Method, req.RequestUri),
            onAfterResponse: res => logger.LogTrace("[Inbound] {StatusCode}", res.StatusCode))
        .OnError(error => logger.LogError(error, "Client error occurred"))
        .OnInitialized(() => logger.LogInformation("Linkit client initialized successfully")),
    jwtToken,
    errorHandler => errorHandler
        .WithCircuitBreaker(failureThreshold: 10, resetTimeout: TimeSpan.FromMinutes(2))
        .WithRetryPolicy(maxRetries: 3, retryDelay: TimeSpan.FromSeconds(1)),
    logger
).ConfigureAwait(false); // ← Always use ConfigureAwait(false) on .NET Framework

Services Reference

ServiceMethodsDescription
client.Products()Create, Query, Update, Delete, ProcessInBatchesCentral product catalog
client.Skus()Query, Update, UpdateStockStock Keeping Units per branch (update-only)
client.Branches()Create, Query, Update, Delete, FindNearbyPhysical locations
client.Customers()Create, Query, Search, Lookup, ProcessInBatches, GroupsCustomer profiles
client.Orders()Query, UpdateStatusOrder management
client.Offers()Create, Query, Update, UpdateStatus, Delete, BulkUpsertPromotions & discounts
client.Brands()Create, Query, Update, DeleteBrand management
client.Categories()Create, Query, Update, DeleteProduct categories
client.Generics()Create, Query, Update, DeleteGeneric entities
client.Health()CheckAsync, PingAsyncSystem health monitoring
client.Integrations()GetByIdAsync, ExecuteAsync, EnableAsync, DisableAsyncPlatform connections

Products

Create

var product = await client.Products()
    .Create()
    .WithId("PROD-001")
    .WithName("Premium Widget", "ويدجت متميز")
    .WithPrice(99.99, vatPercentage: 0.15)
    .WithBarcode("1234567890123")
    .WithAttributes(attr => attr
        .IsFastMoving()
        .Buffer(25)
        .SitemapPriority(0.9))
    .EnableQuickCommerce()
    .AsEnabled()
    .ExecuteAsync();

Query with Filtering

var products = await client.Products()
    .Query()
    .SearchFor("laptop")
    .EnabledOnly()
    .FastMovingOnly()
    .QuickCommerceOnly()
    .Page(1)
    .Take(50)
    .OrderBy("-created")
    .WithMetadata("query_type", "inventory_check")
    .ExecuteAsync();

Batch Processing (Legacy Pattern)

await client.Products()
    .ProcessInBatches()
    .Where(p => p.AveragePrice > 1000)
    .WithBatchSize(100)
    .WithConcurrency(10)
    .Process(async products => {
        foreach (var product in products)
            await ProcessProductAsync(product).ConfigureAwait(false);
    })
    .ProcessAllAsync();

SKU Operations (Update Only)

Important

SKUs are auto-generated when you create products and branches. Never create SKUs manually — only update existing ones.

// Update stock levels
await client.Skus()
    .UpdateStock("EXISTING-SKU-ID", "BRANCH-ID")
    .SetQuantity(150)
    .SetAvailability(true)
    .WithMetadata("reason", "restocking")
    .ExecuteAsync();

// Update SKU with marketplace IDs
await client.Skus()
    .Update("EXISTING-SKU-ID", "BRANCH-ID")
    .WithPrice(899.99)
    .WithStock(quantity: 200, maxQuantity: 1000)
    .WithReorderThreshold(50)
    .WithMarketplaceIds(ids => ids
        .Amazon("AMZ-SKU-123", "B08N5WRWNW")
        .Noon("N123456789", "Z87654321")
        .Salla("SALLA-987")
        .Zid("ZID-654"))
    .ExecuteAsync();

Branches

// Create
var branch = await client.Branches()
    .Create()
    .WithId("STORE-001")
    .WithName("Downtown Store", "متجر وسط المدينة")
    .AtLocation(25.2048, 55.2708)
    .WithStatus(BranchStatus.Published)
    .AsActive()
    .WithWorkingHours(hours => hours
        .Monday("09:00", "21:00")
        .Friday("14:00", "22:00"))
    .ExecuteAsync();

// Find nearby
var nearbyBranches = await client.Branches()
    .FindNearby(lat: 25.2048, lon: 55.2708)
    .WithinRadius(10)
    .ActiveOnly()
    .ExecuteAsync();

Customers

// Create
var customer = await client.Customers()
    .Create()
    .WithName("Ahmad", "Hamdi")
    .WithEmail("ahmad@example.com")
    .WithPhone("+966500000000")
    .AsType("vip")
    .WithStatus("active")
    .ExecuteAsync();

// Lookup
var found = await client.Customers()
    .Lookup()
    .ByEmail("ahmad@example.com")
    .ExecuteAsync();

// Batch process VIP customers
await client.Customers()
    .ProcessInBatches()
    .ActiveOnly()
    .Where(c => c.Type == "vip")
    .WithBatchSize(50)
    .WithConcurrency(10)
    .Process(async customers => {
        foreach (var c in customers)
            await ProcessVipCustomerAsync(c).ConfigureAwait(false);
    })
    .ProcessAllAsync();

Error Handling

using LinkitDotNetLegacy.Exceptions;

try
{
    var sku = await client.Skus().Query().ForProduct("nonexistent").ExecuteAsync();
}
catch (LinkitNotFoundException ex)
{
    Console.WriteLine($"Not found: {ex.ResourceId}");
}
catch (LinkitRateLimitException ex)
{
    Console.WriteLine($"Rate limited — retry after {ex.RetryAfterMs}ms");
}
catch (LinkitValidationException ex)
{
    foreach (var error in ex.Details)
        Console.WriteLine($"  {error.Key}: {error.Value}");
}
catch (LinkitApiException ex)
{
    Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");
}

Exception Hierarchy

ExceptionStatusDescription
LinkitExceptionBase for all SDK errors
LinkitInitializationExceptionInvalid configuration
LinkitApiExceptionAnyAPI error responses
LinkitAuthenticationException401Invalid credentials
LinkitAuthorizationException403Insufficient permissions
LinkitNotFoundException404Resource not found
LinkitValidationException400Request validation failed
LinkitConflictException409State conflict
LinkitRateLimitException429Rate limit exceeded
LinkitServerException5xxServer-side error
LinkitSerializationExceptionJSON parse failure

Resilience

Zero Configuration Required

The resilience pipeline is active by default. Transient errors (5xx, 429, timeouts) are automatically retried with exponential backoff + jitter. The circuit breaker trips after 5 consecutive failures and recovers after 60 seconds.

Override defaults via configuration:

config => config
    .WithRetryPolicy(retry => {
        retry.MaxRetries = 5;
        retry.InitialDelay = TimeSpan.FromMilliseconds(200);
        retry.BackoffMultiplier = 2.0;
    })
    .WithMaxConcurrentRequests(20)

.NET Framework Considerations

ConfigureAwait(false)

Critical for .NET Framework

Always use .ConfigureAwait(false) on all await calls in library code to avoid deadlocks with synchronization contexts (ASP.NET, WinForms, WPF).

// ✅ Correct for .NET Framework library code
var result = await client.Products().Query().ExecuteAsync().ConfigureAwait(false);

// ❌ Risk of deadlock in ASP.NET / WinForms contexts
var result = await client.Products().Query().ExecuteAsync();

Newtonsoft.Json Serialization

The Legacy SDK uses Newtonsoft.Json with cached JsonSerializerSettings for consistent performance:

// The SDK internally uses optimized settings:
// - ContractResolver: CamelCasePropertyNamesContractResolver
// - NullValueHandling: NullValueHandling.Ignore
// - DateFormatHandling: DateFormatHandling.IsoDateFormat
// - Custom converters for DateTime and empty arrays

Performance

OperationAllocationsMean TimeMemory
Create Product548.7 ms5.8 KB
List Products (100)892.3 ms15.2 KB
Update SKU Stock326.4 ms2.4 KB
Process Products (1000)18457.2 ms22.6 KB
Batch Operation (10)11178.9 ms11.3 KB
Customer Search641.2 ms4.2 KB

Benchmarks run on .NET Framework 4.8.1, Intel i7-12700K, 32GB RAM