diff --git a/.gitignore b/.gitignore
index f06235c..bdb46ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
node_modules
dist
+
+dotnet/**/bin
+dotnet/**/obj
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..3971fc7
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,9 @@
+# Repository Purpose
+
+This repository contains a TypeScript implementation of the AliExpress SDK and its .NET port. It serves as a blueprint and reference for building AliExpress integrations in different ecosystems.
+
+# Development Guidelines
+
+- Ensure that every change builds and tests the .NET solution.
+- Run `dotnet build` and `dotnet test` before committing.
+
diff --git a/AliExpressDotnetSdk.sln b/AliExpressDotnetSdk.sln
new file mode 100644
index 0000000..4693177
--- /dev/null
+++ b/AliExpressDotnetSdk.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{1CA53CC0-F2D2-4468-82AB-2C4BEB1B9224}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliExpressSdk", "dotnet\AliExpressSdk\AliExpressSdk.csproj", "{BA27C6F7-3935-43A5-8EDB-F26F43ED0A2C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliExpressSdk.Tests", "dotnet\AliExpressSdk.Tests\AliExpressSdk.Tests.csproj", "{5C6E9E68-3619-4C76-A976-6A6BB38BC4F2}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BA27C6F7-3935-43A5-8EDB-F26F43ED0A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BA27C6F7-3935-43A5-8EDB-F26F43ED0A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BA27C6F7-3935-43A5-8EDB-F26F43ED0A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BA27C6F7-3935-43A5-8EDB-F26F43ED0A2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5C6E9E68-3619-4C76-A976-6A6BB38BC4F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C6E9E68-3619-4C76-A976-6A6BB38BC4F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C6E9E68-3619-4C76-A976-6A6BB38BC4F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C6E9E68-3619-4C76-A976-6A6BB38BC4F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {BA27C6F7-3935-43A5-8EDB-F26F43ED0A2C} = {1CA53CC0-F2D2-4468-82AB-2C4BEB1B9224}
+ {5C6E9E68-3619-4C76-A976-6A6BB38BC4F2} = {1CA53CC0-F2D2-4468-82AB-2C4BEB1B9224}
+ EndGlobalSection
+EndGlobal
diff --git a/dotnet/AliExpressSdk.Tests/AliExpressSdk.Tests.csproj b/dotnet/AliExpressSdk.Tests/AliExpressSdk.Tests.csproj
new file mode 100644
index 0000000..789b27a
--- /dev/null
+++ b/dotnet/AliExpressSdk.Tests/AliExpressSdk.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/AliExpressSdk.Tests/UnitTest1.cs b/dotnet/AliExpressSdk.Tests/UnitTest1.cs
new file mode 100644
index 0000000..98ec673
--- /dev/null
+++ b/dotnet/AliExpressSdk.Tests/UnitTest1.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using AliExpressSdk.Clients;
+using Xunit;
+
+namespace AliExpressSdk.Tests;
+
+public class SigningTests
+{
+ private class TestClient : AEBaseClient
+ {
+ public TestClient() : base("test", "secret", "session") { }
+ public string DoSign(IDictionary p) => Sign(p);
+ }
+
+ [Fact]
+ public void Sign_ComputesExpectedHash()
+ {
+ var client = new TestClient();
+ var parameters = new Dictionary
+ {
+ ["method"] = "/auth/token/create",
+ ["app_key"] = "test",
+ ["session"] = "session",
+ ["timestamp"] = "12345",
+ ["simplify"] = "true",
+ ["sign_method"] = "sha256"
+ };
+ var sign = client.DoSign(parameters);
+ Assert.Equal("083482F9A0CE8559B567E46222AA1401B09BBDACC409D0BDA77A9A385A0BD31C", sign);
+ }
+}
diff --git a/dotnet/AliExpressSdk/AliExpressSdk.csproj b/dotnet/AliExpressSdk/AliExpressSdk.csproj
new file mode 100644
index 0000000..bb23fb7
--- /dev/null
+++ b/dotnet/AliExpressSdk/AliExpressSdk.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/dotnet/AliExpressSdk/Clients/AEBaseClient.cs b/dotnet/AliExpressSdk/Clients/AEBaseClient.cs
new file mode 100644
index 0000000..3580a13
--- /dev/null
+++ b/dotnet/AliExpressSdk/Clients/AEBaseClient.cs
@@ -0,0 +1,135 @@
+using System.Linq;
+using System.Globalization;
+using System.Net.Http;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using AliExpressSdk.Models;
+
+namespace AliExpressSdk.Clients;
+
+public class AEBaseClient
+{
+ private readonly HttpClient _httpClient;
+ public string AppKey { get; }
+ public string AppSecret { get; }
+ public string Session { get; }
+
+ private const string TopApiUrl = "https://api-sg.aliexpress.com/sync";
+ private const string OpApiUrl = "https://api-sg.aliexpress.com/rest";
+ private const string SignMethod = "sha256";
+
+ public AEBaseClient(string appKey, string appSecret, string session, HttpClient? httpClient = null)
+ {
+ AppKey = appKey;
+ AppSecret = appSecret;
+ Session = session;
+ _httpClient = httpClient ?? new HttpClient();
+ }
+
+ protected string Sign(IDictionary parameters)
+ {
+ var p = new Dictionary(parameters);
+ var baseString = string.Empty;
+ if (p.TryGetValue("method", out var method) && method.Contains('/'))
+ {
+ baseString = method;
+ p.Remove("method");
+ }
+
+ foreach (var kv in p.Where(kv => kv.Value != null).OrderBy(kv => kv.Key))
+ {
+ baseString += kv.Key + kv.Value;
+ }
+
+ using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(AppSecret));
+ var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(baseString));
+ return string.Concat(hash.Select(b => b.ToString("X2", CultureInfo.InvariantCulture)));
+ }
+
+ protected string Assemble(IDictionary parameters)
+ {
+ var p = new Dictionary(parameters);
+ var baseUrl = p["method"].Contains('/') ? $"{OpApiUrl}{p["method"]}" : TopApiUrl;
+ if (p["method"].Contains('/'))
+ {
+ p.Remove("method");
+ }
+
+ var query = string.Join("&", p
+ .Where(kv => kv.Value != null)
+ .OrderBy(kv => kv.Key)
+ .Select((kv, idx) =>
+ {
+ var prefix = idx == 0 ? "?" : "&";
+ return prefix + Uri.EscapeDataString(kv.Key) + "=" + Uri.EscapeDataString(kv.Value);
+ }));
+
+ return baseUrl + query;
+ }
+
+ protected async Task> Call(IDictionary parameters)
+ {
+ var url = Assemble(parameters);
+ try
+ {
+ var response = await _httpClient.GetAsync(url);
+ if (!response.IsSuccessStatusCode)
+ {
+ return new Result { Ok = false, Message = $"HTTP Error: {(int)response.StatusCode} {response.ReasonPhrase}" };
+ }
+ var content = await response.Content.ReadAsStringAsync();
+ var doc = JsonDocument.Parse(content);
+ var root = doc.RootElement;
+ if (root.TryGetProperty("error_response", out var error))
+ {
+ return new Result
+ {
+ Ok = false,
+ Message = "Bad request",
+ ErrorResponse = error,
+ RequestId = error.GetProperty("request_id").GetString()
+ };
+ }
+ return new Result { Ok = true, Data = root };
+ }
+ catch (Exception ex)
+ {
+ return new Result { Ok = false, Message = ex.Message };
+ }
+ }
+
+ protected async Task> Execute(string method, IDictionary parameters)
+ {
+ var p = new Dictionary(parameters)
+ {
+ ["method"] = method,
+ ["session"] = Session,
+ ["app_key"] = AppKey,
+ ["simplify"] = "true",
+ ["sign_method"] = SignMethod,
+ ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture)
+ };
+ p["sign"] = Sign(p);
+ return await Call(p);
+ }
+
+ public Task> CallApiDirectly(string method, IDictionary parameters)
+ {
+ if (string.IsNullOrWhiteSpace(method))
+ {
+ return Task.FromResult(new Result { Ok = false, Message = "Method parameter is required" });
+ }
+ var p = new Dictionary(parameters)
+ {
+ ["method"] = method,
+ ["session"] = Session,
+ ["app_key"] = AppKey,
+ ["simplify"] = "true",
+ ["sign_method"] = SignMethod,
+ ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture)
+ };
+ p["sign"] = Sign(p);
+ return Call(p);
+ }
+}
diff --git a/dotnet/AliExpressSdk/Clients/AESystemClient.cs b/dotnet/AliExpressSdk/Clients/AESystemClient.cs
new file mode 100644
index 0000000..5565eda
--- /dev/null
+++ b/dotnet/AliExpressSdk/Clients/AESystemClient.cs
@@ -0,0 +1,25 @@
+using System.Net.Http;
+using System.Text.Json;
+using AliExpressSdk.Models;
+
+namespace AliExpressSdk.Clients;
+
+public class AESystemClient : AEBaseClient
+{
+ public AESystemClient(string appKey, string appSecret, string session, HttpClient? httpClient = null)
+ : base(appKey, appSecret, session, httpClient)
+ {
+ }
+
+ public Task> GenerateSecurityToken(IDictionary args)
+ => Execute("/auth/token/security/create", args);
+
+ public Task> GenerateToken(IDictionary args)
+ => Execute("/auth/token/create", args);
+
+ public Task> RefreshSecurityToken(IDictionary args)
+ => Execute("/auth/token/security/refresh", args);
+
+ public Task> RefreshToken(IDictionary args)
+ => Execute("/auth/token/refresh", args);
+}
diff --git a/dotnet/AliExpressSdk/Clients/AffiliateClient.cs b/dotnet/AliExpressSdk/Clients/AffiliateClient.cs
new file mode 100644
index 0000000..9ddb6c8
--- /dev/null
+++ b/dotnet/AliExpressSdk/Clients/AffiliateClient.cs
@@ -0,0 +1,49 @@
+using System.Net.Http;
+using System.Text.Json;
+using AliExpressSdk.Models;
+
+namespace AliExpressSdk.Clients;
+
+public class AffiliateClient : AESystemClient
+{
+ public AffiliateClient(string appKey, string appSecret, string session, HttpClient? httpClient = null)
+ : base(appKey, appSecret, session, httpClient)
+ {
+ }
+
+ public Task> GenerateAffiliateLinks(IDictionary args)
+ => Execute("aliexpress.affiliate.link.generate", args);
+
+ public Task> GetCategories(IDictionary args)
+ => Execute("aliexpress.affiliate.category.get", args);
+
+ public Task> FeaturedPromoInfo(IDictionary args)
+ => Execute("aliexpress.affiliate.featuredpromo.get", args);
+
+ public Task> FeaturedPromoProducts(IDictionary args)
+ => Execute("aliexpress.affiliate.featuredpromo.products.get", args);
+
+ public Task> GetHotProductsDownload(IDictionary args)
+ => Execute("aliexpress.affiliate.hotproduct.download", args);
+
+ public Task> GetHotProducts(IDictionary args)
+ => Execute("aliexpress.affiliate.hotproduct.query", args);
+
+ public Task> OrderInfo(IDictionary args)
+ => Execute("aliexpress.affiliate.order.get", args);
+
+ public Task> OrdersList(IDictionary args)
+ => Execute("aliexpress.affiliate.order.list", args);
+
+ public Task> OrdersListByIndex(IDictionary args)
+ => Execute("aliexpress.affiliate.order.listbyindex", args);
+
+ public Task> ProductDetails(IDictionary args)
+ => Execute("aliexpress.affiliate.productdetail.get", args);
+
+ public Task> QueryProducts(IDictionary args)
+ => Execute("aliexpress.affiliate.product.query", args);
+
+ public Task> SmartMatchProducts(IDictionary args)
+ => Execute("aliexpress.affiliate.product.smartmatch", args);
+}
diff --git a/dotnet/AliExpressSdk/Models/Result.cs b/dotnet/AliExpressSdk/Models/Result.cs
new file mode 100644
index 0000000..5e8536d
--- /dev/null
+++ b/dotnet/AliExpressSdk/Models/Result.cs
@@ -0,0 +1,12 @@
+using System.Text.Json;
+
+namespace AliExpressSdk.Models;
+
+public class Result
+{
+ public bool Ok { get; set; }
+ public string? Message { get; set; }
+ public T? Data { get; set; }
+ public JsonElement? ErrorResponse { get; set; }
+ public string? RequestId { get; set; }
+}