LinkitDotNetLegacy 0.0.4
dotnet add package LinkitDotNetLegacy --version 0.0.4
NuGet\Install-Package LinkitDotNetLegacy -Version 0.0.4
<PackageReference Include="LinkitDotNetLegacy" Version="0.0.4" />
<PackageVersion Include="LinkitDotNetLegacy" Version="0.0.4" />
<PackageReference Include="LinkitDotNetLegacy" />
paket add LinkitDotNetLegacy --version 0.0.4
#r "nuget: LinkitDotNetLegacy, 0.0.4"
#:package LinkitDotNetLegacy@0.0.4
#addin nuget:?package=LinkitDotNetLegacy&version=0.0.4
#tool nuget:?package=LinkitDotNetLegacy&version=0.0.4
LinkitDotNetLegacy SDK - High-Performance .NET Framework 4.8.1 Client
A modern, high-performance fluent SDK for the Linkit API built with .NET Framework 4.8.1 and C# 13 compiler features, emphasizing optimized patterns, type safety, and developer experience using builder pattern.
Key Features
- Optimized JSON Serialization: Uses Newtonsoft.Json with cached settings for reliable performance
- Fluent API Design: Intuitive, chainable methods that hide API complexity
- Type-Safe Models: Full nullable reference types support with comprehensive validation
- Efficient Pagination: Task-based async patterns for memory-efficient data retrieval
- Built-in Retry Logic: Configurable exponential backoff with circuit breaker patterns
- Rate Limiting: Thread-safe request throttling to prevent API overload
- Minimal Dependencies: Only System.Net.Http, Newtonsoft.Json, and System.ComponentModel.DataAnnotations
Installation
Package Manager Console
Install-Package LinkitDotNetLegacy -Version 0.0.4
.NET CLI
dotnet add package LinkitDotNetLegacy --version 0.0.4
PackageReference
<PackageReference Include="LinkitDotNetLegacy" Version="0.0.4" />
Supported C# 13 Features
While targeting .NET Framework 4.8.1, LinkitDotNetLegacy leverages modern C# 13 compiler features that don't require runtime support:
- Records: Full support for record types (with polyfills where needed)
- Pattern Matching: Switch expressions and pattern matching
- File-scoped Namespaces: Cleaner code organization
- Global Usings: Reduced boilerplate
- Nullable Reference Types: Full nullable annotations
- Target-typed new: Simplified object creation
Note: Features requiring .NET Core runtime support (like IAsyncEnumerable) are implemented using alternative patterns compatible with .NET Framework 4.8.1.
Quick Start
using LinkitDotNetLegacy.Client;
using LinkitDotNetLegacy.Configuration;
// Initialize client with JWT authentication
using var client = await LinkitClient.CreateClientAsync(
config => config
.WithBaseUrl("https://linkit.works/api/v1")
.WithTimeout(TimeSpan.FromSeconds(30))
.WithRetryPolicy(retry => retry.MaxRetries = 3),
jwtToken: "client-jwt-token"
);
// Quick setup for initial configuration
// This should be for testing purposes only
// Never actually use QuickSetup in production
var setupResult = await client
.QuickSetup()
.WithSampleProducts(5)
.WithSampleBranches(2)
.CreateSkusAutomatically() // SKUs are auto-created - never create manually, you can omit this line.
.ExecuteAsync(jwtToken);
// 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();
Important: SKU Management
⚠️ Critical: SKUs are automatically created by the system when you create products and branches. NEVER create SKUs manually - only update existing SKUs that have been auto-generated.
// ❌ WRONG - Never create SKUs manually
// await client.Skus().Create()... // DO NOT USE
// ✅ CORRECT - Update existing auto-generated SKUs only
await client.Skus()
.UpdateStock("AUTO-GENERATED-SKU-ID", "BRANCH-ID")
.SetQuantity(150)
.SetAvailability(true)
.ExecuteAsync();
// ✅ CORRECT - Update SKU details
await client.Skus()
.Update("AUTO-GENERATED-SKU-ID", "BRANCH-ID")
.WithPrice(129.99)
.WithStock(100, maxQuantity: 500)
.WithMarketplaceIds(ids => ids
.Amazon("AMZ-123", "B08N5WRWNW")
.Noon("NOON-456", "Z789"))
.ExecuteAsync();
Understanding SKU Auto-Generation
When you create a product and branches, the system automatically:
- Generates SKUs for each product-branch combination
- Assigns unique SKU IDs based on your product and branch IDs
- Sets initial stock levels and availability
// Example: Creating a product and branch will auto-generate SKUs
var product = await client.Products()
.Create()
.WithId("LAPTOP-001")
.WithName("Gaming Laptop", "لابتوب ألعاب")
.WithPrice(1299.99)
.ExecuteAsync();
var branch = await client.Branches()
.Create()
.WithId("STORE-001")
.WithName("Main Store", "المتجر الرئيسي")
.AtLocation(25.2048, 55.2708)
.ExecuteAsync();
// SKU is automatically created with ID pattern
// You can now query or update it:
var skus = await client.Skus()
.Query()
.ForProduct("LAPTOP-001")
.InBranch("STORE-001")
.ExecuteAsync();
// Update the auto-generated SKU
if (skus.Data.Any())
{
var sku = skus.Data.First();
await client.Skus()
.UpdateStock(sku.IvId, sku.BranchId)
.SetQuantity(50)
.ExecuteAsync();
}
Architecture Decisions
Performance-First Design
The SDK prioritizes performance through several key decisions:
- Optimized Serialization: All JSON serialization uses Newtonsoft.Json with cached JsonSerializerSettings for consistent performance
- Value Types: Using
readonly struct
for immutable data likeLocation
andPaginationMeta
to avoid heap allocations where supported - Object Pooling: Internal HTTP request/response handling uses efficient buffer management
- Async Patterns: Task-based async for efficient data retrieval without blocking
Type Safety & Developer Experience
- Nullable Reference Types: Full C# nullable annotations prevent null reference exceptions
- Fluent Builders: Chainable API design for intuitive operation construction
- Comprehensive Validation: Built-in validation with custom validators
- Metadata Support: Per-operation metadata for tracking and debugging
Resilience & Reliability
- Retry Policy: Configurable exponential backoff with jitter for transient failures
- Circuit Breaker: Automatic failure detection and recovery
- Error Handling: Typed exceptions for different failure scenarios
- Cancellation Support: All async operations accept CancellationToken
Core Operations
Product Management
using LinkitDotNetLegacy.Models;
// Query products with advanced filtering
var products = await client.Products()
.Query()
.SearchFor("laptop")
.EnabledOnly()
.FastMovingOnly()
.QuickCommerceOnly()
.Page(1)
.Take(50)
.OrderBy("-created")
.WithMetadata("query_type", "inventory_check")
.ExecuteAsync();
// Update product with validation
await client.Products()
.Update("PROD-001")
.WithPrice(1199.99)
.WithAttributes(attr => attr
.IsFastMoving()
.Buffer(25)
.SitemapPriority(0.9))
.Validate(async () => {
// Custom validation logic
return await ValidatePriceAsync();
}, "Price validation failed")
.ExecuteAsync();
// Process large product datasets efficiently
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();
Branch Operations
// Create branch with comprehensive configuration
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")
.Saturday("10:00", "20:00")
.ClosedOn(DayOfWeek.Sunday))
.WithMapsIds(
googleMapsId: "ChIJ_1234567890",
appleMapsId: "AMAP_1234567890")
.WithImages(
heroImage: "https://cdn.example.com/hero.jpg",
bannerImage: "https://cdn.example.com/banner.jpg")
.ExecuteAsync();
// Find nearby branches
var nearbyBranches = await client.Branches()
.FindNearby(lat: 25.2048, lon: 55.2708)
.WithinRadius(10) // km
.ActiveOnly()
.WithStatus(BranchStatus.Published)
.ExecuteAsync();
// Update branch with file uploads
await client.Branches()
.Update("STORE-001")
.UpdateHeroImage(FileUpload.FromBytes(imageData, "hero.jpg"))
.UpdatePhotos(
FileUpload.FromPath("/path/to/photo1.jpg"),
FileUpload.FromStream(imageStream, "photo2.jpg"))
.WithAutoDisposeFiles()
.ExecuteAsync();
SKU Operations (Update Only)
// Query existing auto-generated SKUs
var skus = await client.Skus()
.Query()
.ForProduct("PROD-001")
.AvailableOnly()
.Page(1)
.Take(20)
.ExecuteAsync();
// Update stock levels for auto-generated SKU
await client.Skus()
.UpdateStock("EXISTING-SKU-ID", "BRANCH-ID")
.SetQuantity(150)
.SetAvailability(true)
.WithMetadata("reason", "restocking")
.WithMetadata("updated_by", "inventory_system")
.ExecuteAsync();
// Mark SKU as out of stock
await client.Skus()
.UpdateStock("EXISTING-SKU-ID", "BRANCH-ID")
.MarkAsOutOfStock()
.WithMetadata("reason", "inventory_shortage")
.ExecuteAsync();
// Update SKU with marketplace integration
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();
// Query low stock items
var lowStockSkus = await client.Skus()
.Query()
.AvailableOnly()
.LowStockOnly()
.ForBranch("STORE-001")
.ExecuteAsync();
Customer Management
// Create customer with validation
var customer = await client.Customers()
.Create()
.WithName("John", "Doe")
.WithEmail("john.doe@example.com")
.WithPhone("+1234567890")
.WithBirthdate("1985-03-15")
.WithGender("male")
.AsType("vip")
.WithStatus("active")
.Validate(async () => {
// Validate age is 18+
return await ValidateCustomerAgeAsync();
}, "Customer must be at least 18 years old")
.WithMetadata("source", "api")
.WithMetadata("registration_channel", "online")
.ExecuteAsync();
// Manage customer addresses
var address = await client.Customers()
.ForCustomer(customer.Id)
.CreateAddress()
.WithLabel("Home")
.WithAddress("123 Main Street", "Apt 4B")
.InLocation("Dubai", "Dubai")
.InCountry("AE")
.AsDefault()
.ExecuteAsync();
// Search customers with advanced criteria
var searchResults = await client.Customers()
.Search()
.WithQuery("@example.com")
.WithStatuses("active", "pending")
.WithTypes("individual", "vip")
.CreatedBetween(
DateTime.UtcNow.AddMonths(-6),
DateTime.UtcNow)
.Page(1)
.Take(50)
.ExecuteAsync();
// Customer lookup operations
var customerByEmail = await client.Customers()
.Lookup()
.ByEmail("john.doe@example.com")
.ExecuteAsync();
var customerByPhone = await client.Customers()
.Lookup()
.ByPhone("+1234567890")
.ExecuteAsync();
// Process customer data efficiently
await client.Customers()
.ProcessInBatches()
.ActiveOnly()
.Where(c => c.Type == "vip")
.WithBatchSize(50)
.WithConcurrency(10)
.Process(async customers => {
foreach (var customer in customers)
await ProcessVipCustomerAsync(customer);
})
.ProcessAllAsync();
Customer Groups
// Create customer group
var group = await client.Customers()
.Groups()
.Create()
.WithName("VIP Customers")
.WithDescription("High-value customers with special privileges")
.ExecuteAsync();
// List groups with pagination
var groups = await client.Customers()
.Groups()
.List()
.Page(1)
.Take(20)
.ExecuteAsync();
// Update group
await client.Customers()
.Groups()
.Update(group.Id)
.WithName("Premium VIP Customers")
.WithDescription("Updated description")
.ExecuteAsync();
Advanced Features
Batch Operations
var batchResult = await client.Batch()
.WithContinueOnError(true)
.WithTimeout(TimeSpan.FromMinutes(5))
.CreateProduct(p => p
.WithId("BATCH-001")
.WithName("Batch Product 1", "منتج دفعة 1")
.WithPrice(99.99))
.CreateProduct(p => p
.WithId("BATCH-002")
.WithName("Batch Product 2", "منتج دفعة 2")
.WithPrice(149.99))
.CreateBranch(b => b
.WithId("BATCH-STORE")
.WithName("Batch Store", "متجر دفعة")
.AtLocation(25.2, 55.3))
// Note: SKUs will be auto-created for these products and branches
.ExecuteAsync();
Console.WriteLine($"Success: {batchResult.SuccessfulOperations}/{batchResult.TotalOperations}");
Error Handling
using LinkitDotNetLegacy.Exceptions;
using LinkitDotNetLegacy.Logging;
public class ResilientLinkitOperation<T> where T : class
{
private readonly LinkitClient _client;
private readonly ILinkitLogger<ResilientLinkitOperation<T>> _logger;
public async Task<Result<T>> ExecuteWithFallbackAsync(
Func<LinkitClient, Task<T>> primaryOperation,
Func<LinkitClient, Task<T>> fallbackOperation,
CancellationToken cancellationToken = default)
{
try
{
return Result<T>.Success(await primaryOperation(_client).ConfigureAwait(false));
}
catch (LinkitValidationException ex)
{
_logger.LogWarning("Validation failed: {Details}", ex.Details);
return Result<T>.Failure($"Validation error: {string.Join(", ", ex.Details.Select(d => $"{d.Key}: {d.Value}"))}");
}
catch (LinkitConflictException ex)
{
_logger.LogInformation("Resource conflict, attempting fallback: {Message}", ex.Message);
return Result<T>.Success(await fallbackOperation(_client).ConfigureAwait(false));
}
catch (LinkitNotFoundException ex)
{
_logger.LogError("Resource not found: {ResourceId}", ex.ResourceId);
return Result<T>.Failure($"Resource {ex.ResourceId} not found");
}
catch (LinkitApiException ex) when (ex.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
{
_logger.LogWarning("Service unavailable, executing fallback");
return Result<T>.Success(await fallbackOperation(_client).ConfigureAwait(false));
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error in Linkit operation");
throw;
}
}
}
Advanced Configuration
using LinkitDotNetLegacy.Logging;
public class EnterpriseGradeLinkitConfiguration
{
public static async Task<LinkitClient> CreateResilientClientAsync(
string jwtToken,
ILinkitLogger<LinkitClient> logger,
CancellationToken cancellationToken = default)
{
return 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} in {ElapsedMs}ms",
res.StatusCode,
res.Headers.TryGetValues("X-Response-Time", out var times) ? times.FirstOrDefault() : "N/A"))
.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))
.OnException<TaskCanceledException>(async ex => {
logger.LogWarning("Request timeout, will retry: {Message}", ex.Message);
return await Task.FromResult(true);
})
.OnException<HttpRequestException>(async ex => {
logger.LogError("Network error, will retry: {Message}", ex.Message);
return await Task.FromResult(true);
})
.WithDefaultHandler(async ex => {
logger.LogError(ex, "Unhandled exception in Linkit operation");
await Task.CompletedTask;
}),
logger,
cancellationToken: cancellationToken
).ConfigureAwait(false);
}
}
Context and Metadata
// Add global context for all operations
client
.WithContext("environment", "production")
.WithContext("version", Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0")
.WithContext("session_id", Guid.NewGuid().ToString())
.WithContext("user_agent", "enterprise-app");
// Per-operation metadata with tracing
await client.Products()
.Create()
.WithId("PROD-TRACE-001")
.WithName("Traced Product", "منتج متتبع")
.WithPrice(99.99)
.WithMetadata("trace_id", Guid.NewGuid().ToString())
.WithMetadata("span_id", Guid.NewGuid().ToString())
.WithMetadata("import_batch", batchId)
.WithMetadata("source", "integration")
.ExecuteAsync();
Enterprise-Grade Usage Patterns
Multi-Tenant Configuration
public class MultiTenantLinkitService
{
private readonly ConcurrentDictionary<string, LinkitClient> _tenantClients = new ConcurrentDictionary<string, LinkitClient>();
private readonly ILinkitLogger<MultiTenantLinkitService> _logger;
public async Task<LinkitClient> GetOrCreateClientForTenantAsync(
string tenantId,
string jwtToken,
CancellationToken cancellationToken = default)
{
if (_tenantClients.TryGetValue(tenantId, out var existingClient))
return existingClient;
var newClient = await LinkitClient.CreateClientAsync(
config => config
.WithBaseUrl($"https://linkit.works/api/v1")
.WithDefaultHeader("X-Tenant-Id", tenantId)
.WithTimeout(TimeSpan.FromMinutes(1)),
jwtToken,
logger: _logger,
cancellationToken: cancellationToken
).ConfigureAwait(false);
_tenantClients.TryAdd(tenantId, newClient);
return newClient;
}
}
Advanced Monitoring Integration
public class MonitoredLinkitOperations
{
private readonly LinkitClient _client;
private readonly ILinkitLogger<MonitoredLinkitOperations> _logger;
private readonly IMetrics _metrics;
public async Task<ProductResponse> CreateProductWithMetricsAsync(
ProductRequest request,
CancellationToken cancellationToken = default)
{
var stopwatch = Stopwatch.StartNew();
try
{
var product = await _client.Products()
.Create()
.WithId(request.IvId)
.WithName(request.NameEn ?? "", request.NameAr ?? "")
.WithPrice(request.AveragePrice ?? 0)
.WithMetadata("trace_id", Guid.NewGuid().ToString())
.ExecuteAsync(cancellationToken)
.ConfigureAwait(false);
_metrics.Measure.Counter.Increment("product_created");
_metrics.Measure.Timer.Time("product_creation_duration", stopwatch.ElapsedMilliseconds);
return product;
}
catch (LinkitApiException ex)
{
_metrics.Measure.Counter.Increment("product_creation_failed");
throw;
}
finally
{
stopwatch.Stop();
}
}
}
Performance Benchmarks
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
Testing
public class LinkitIntegrationTests
{
private readonly MockHttpMessageHandler _mockHttp;
private readonly LinkitClient _client;
private readonly ILinkitLogger<LinkitIntegrationTests> _logger;
public LinkitIntegrationTests()
{
var loggerProvider = new ConsoleLoggerProvider(LogLevel.Debug);
_logger = new LinkitLogger<LinkitIntegrationTests>(loggerProvider);
_mockHttp = new MockHttpMessageHandler();
ConfigureMockResponses();
_client = new LinkitClient(
LinkitConfiguration.Development,
new HttpClient(_mockHttp)
);
}
private void ConfigureMockResponses()
{
// Mock product responses
_mockHttp.When("/api/v1/products/*")
.Respond("application/json", @"{
'id': 'test-001',
'ivId': 'TEST-001',
'nameEn': 'Test Product',
'averagePrice': 99.99
}");
// Mock SKU responses (auto-generated)
_mockHttp.When("/api/v1/skus/*")
.Respond("application/json", @"{
'id': 'sku-auto-001',
'ivId': 'AUTO-SKU-001',
'productId': 'TEST-001',
'branchId': 'BRANCH-001',
'qty': 100
}");
}
[Fact]
public async Task Should_Update_AutoGenerated_Sku_Successfully()
{
// Arrange
var skuId = "AUTO-SKU-001";
var branchId = "BRANCH-001";
// Act
var result = await _client.Skus()
.UpdateStock(skuId, branchId)
.SetQuantity(150)
.ExecuteAsync();
// Assert
Assert.NotNull(result);
Assert.Equal(150, result.Qty);
}
}
Requirements
- .NET Framework 4.8.1
- C# 13 language support (via modern compiler)
- Valid JWT authentication token from Linkit
- Newtonsoft.Json 13.0.3 or later
Compatibility Notes
LinkitDotNetLegacy maintains API compatibility with LinkitDotNet while targeting .NET Framework 4.8.1. Some features have been adapted:
- JSON Serialization: Uses Newtonsoft.Json instead of System.Text.Json
- Async Patterns: Uses Task-based patterns instead of IAsyncEnumerable
- HTTP Client: Configured for optimal performance on .NET Framework
- C# Features: Leverages C# 13 compiler features that don't require runtime support
License
MIT License - see LICENSE file for details
Support
- API Documentation: https://linkit.works/docs
- SDK Issues: https://github.com/linkit/sdk-dotnet-legacy/issues
- Email: support@linkit.works
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET Framework | net48 is compatible. net481 was computed. |
-
.NETFramework 4.8
- Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
- Newtonsoft.Json (>= 13.0.3)
- System.ComponentModel.Annotations (>= 5.0.0)
- System.Memory (>= 4.5.5)
- System.Net.Http (>= 4.3.4)
- System.Threading.Tasks.Extensions (>= 4.5.4)
- System.ValueTuple (>= 4.5.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
0.0.4 | 139 | 8/14/2025 |
v0.0.4:
- Full .NET Framework 4.8 compatibility
- Optimized Newtonsoft.Json serialization with cached settings
- Enterprise-grade error handling and resilience patterns
- Support for modern C# 13 compiler features
- Comprehensive SKU management (update-only, auto-generated)
- Enhanced batch operations and pagination
v0.0.3:
- Added customer management features
- Improved branch operations
- Performance optimizations
v0.0.2:
- Initial release with core functionality