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
| Feature | LinkitDotNet (Modern) | LinkitDotNetLegacy |
|---|---|---|
| Target | .NET 10+ | .NET Framework 4.8 |
| JSON | System.Text.Json source gen | Newtonsoft.Json cached settings |
| Streaming | IAsyncEnumerable | Task-based batch processing |
| External Deps | Zero | Newtonsoft.Json, polyfills |
| NuGet Package | LinkitDotNet | LinkitDotNetLegacy |
ConfigureAwait | Not needed | Required (ConfigureAwait(false)) |
| Allocations | 3 per create op | 5 per create op |
| Namespace | LinkitDotNet.* | LinkitDotNetLegacy.* |
| Disposal | await using | using |
Installation
nuget install LinkitDotNetLegacydotnet add package LinkitDotNetLegacy<PackageReference Include="LinkitDotNetLegacy" Version="0.0.4" />Dependencies
The Legacy SDK ships with these pinned dependencies:
| Package | Version | Purpose |
|---|---|---|
Newtonsoft.Json | 13.0.3 | JSON serialization |
System.Net.Http | 4.3.4 | HTTP transport |
System.Threading.Tasks.Extensions | 4.5.4 | ValueTask support |
System.Memory | 4.5.5 | Span<T> polyfills |
Microsoft.Bcl.AsyncInterfaces | 8.0.0 | Async interface polyfills |
System.ComponentModel.Annotations | 5.0.0 | Validation 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 FrameworkServices Reference
| Service | Methods | Description |
|---|---|---|
client.Products() | Create, Query, Update, Delete, ProcessInBatches | Central product catalog |
client.Skus() | Query, Update, UpdateStock | Stock Keeping Units per branch (update-only) |
client.Branches() | Create, Query, Update, Delete, FindNearby | Physical locations |
client.Customers() | Create, Query, Search, Lookup, ProcessInBatches, Groups | Customer profiles |
client.Orders() | Query, UpdateStatus | Order management |
client.Offers() | Create, Query, Update, UpdateStatus, Delete, BulkUpsert | Promotions & discounts |
client.Brands() | Create, Query, Update, Delete | Brand management |
client.Categories() | Create, Query, Update, Delete | Product categories |
client.Generics() | Create, Query, Update, Delete | Generic entities |
client.Health() | CheckAsync, PingAsync | System health monitoring |
client.Integrations() | GetByIdAsync, ExecuteAsync, EnableAsync, DisableAsync | Platform 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
| Exception | Status | Description |
|---|---|---|
LinkitException | — | Base for all SDK errors |
LinkitInitializationException | — | Invalid configuration |
LinkitApiException | Any | API error responses |
LinkitAuthenticationException | 401 | Invalid credentials |
LinkitAuthorizationException | 403 | Insufficient permissions |
LinkitNotFoundException | 404 | Resource not found |
LinkitValidationException | 400 | Request validation failed |
LinkitConflictException | 409 | State conflict |
LinkitRateLimitException | 429 | Rate limit exceeded |
LinkitServerException | 5xx | Server-side error |
LinkitSerializationException | — | JSON 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 arraysPerformance
| Operation | Allocations | Mean Time | Memory |
|---|---|---|---|
| Create Product | 5 | 48.7 ms | 5.8 KB |
| List Products (100) | 8 | 92.3 ms | 15.2 KB |
| Update SKU Stock | 3 | 26.4 ms | 2.4 KB |
| Process Products (1000) | 18 | 457.2 ms | 22.6 KB |
| Batch Operation (10) | 11 | 178.9 ms | 11.3 KB |
| Customer Search | 6 | 41.2 ms | 4.2 KB |
Benchmarks run on .NET Framework 4.8.1, Intel i7-12700K, 32GB RAM
C# / .NET (Modern)
Production-grade C# SDK for the Linkit API — .NET 10, System.Text.Json source generation, IAsyncEnumerable, zero external dependencies.
TypeScript SDK
Type-safe TypeScript SDK for the Linkit API — works in Node.js, Deno, and Bun with native fetch, full type inference, and npm distribution.