ExisOne.Client
0.7.0
dotnet add package ExisOne.Client --version 0.7.0
NuGet\Install-Package ExisOne.Client -Version 0.7.0
<PackageReference Include="ExisOne.Client" Version="0.7.0" />
<PackageVersion Include="ExisOne.Client" Version="0.7.0" />
<PackageReference Include="ExisOne.Client" />
paket add ExisOne.Client --version 0.7.0
#r "nuget: ExisOne.Client, 0.7.0"
#:package ExisOne.Client@0.7.0
#addin nuget:?package=ExisOne.Client&version=0.7.0
#tool nuget:?package=ExisOne.Client&version=0.7.0
Software Activation System
A comprehensive software licensing and activation system built with .NET 8, Entity Framework Core, and SQL Server.
Features
- Product management with versioning
- License generation and activation
- Hardware-locked licensing
- License validation and expiration
- License deactivation
- License history tracking
- Admin dashboard for license management
- Corporate/Multi-Seat Licensing - Support for enterprise licenses with configurable seat limits
- Offline Licensing - RSA-signed activation codes for customers without internet access
Project Structure
SoftwareActivationSystem.Api: ASP.NET Core Web API project containing the license management systemSoftwareActivationSystem.Core: Core library containing shared interfaces and modelsSoftwareActivationSystem.HardwareId: Library for generating unique hardware IDs
Setup
- Update the database connection string in
appsettings.jsonwith your Azure SQL Database credentials:
{
"ConnectionStrings": {
"DefaultConnection": "Server=exiswwwdb.database.windows.net;Database=exiswwwdb;User Id=your_username;Password=your_password;"
}
}
- Build and run the solution:
dotnet build
dotnet run --project SoftwareActivationSystem.Api
- Access the web interface at
https://localhost:5001to generate activation keys.
API Endpoints
Products
GET /api/products- Get all productsGET /api/products/{id}- Get a specific productPOST /api/products- Create a new productPUT /api/products/{id}- Update a productDELETE /api/products/{id}- Delete a product
Activation Keys
POST /api/activationkey/generate- Generate a new activation key{ "productName": "string", "email": "string", "validityDays": number }POST /api/activationkey/generate- Generate an offline activation code (whenhardwareIdis provided){ "productName": "string", "email": "string", "validityDays": number, "hardwareId": "string (customer's hardware ID)" }Returns a Base32-encoded, RSA-signed offline code that can be validated without server connection.
Licenses
GET /api/license- Get all licensesPOST /api/license/activate- Activate a license{ "activationKey": "string", "email": "string", "hardwareId": "string", "productName": "string" }POST /api/license/validate- Validate a license{ "activationKey": "string", "hardwareId": "string" }POST /api/license/check- Check if a license is valid{ "activationKey": "string", "hardwareId": "string" }POST /api/license/deactivate- Deactivate a license{ "licenseKey": "string", "productName": "string", "hardwareId": "string" }PUT /api/license/{activationKey}/expiration- Update license expiration date{ "expirationDate": "2023-12-31T23:59:59Z" }GET /api/license/{activationKey}/history- Get license history
Integration Guide
1. Hardware ID Generation
public static string GetHardwareId()
{
var cpuId = GetCpuId();
var motherboardId = GetMotherboardId();
var diskId = GetDiskId();
// Combine and hash the hardware IDs
var combinedId = $"{cpuId}|{motherboardId}|{diskId}";
using (var sha256 = SHA256.Create())
{
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(combinedId));
return BitConverter.ToString(hash).Replace("-", "").Substring(0, 32);
}
}
private static string GetCpuId()
{
try
{
var mbs = new ManagementObjectSearcher("Select ProcessorId From Win32_processor");
var mbsList = mbs.Get();
foreach (var mo in mbsList)
{
return mo["ProcessorId"].ToString();
}
}
catch { }
return string.Empty;
}
private static string GetMotherboardId()
{
try
{
var mbs = new ManagementObjectSearcher("Select SerialNumber From Win32_BaseBoard");
var mbsList = mbs.Get();
foreach (var mo in mbsList)
{
return mo["SerialNumber"].ToString();
}
}
catch { }
return string.Empty;
}
private static string GetDiskId()
{
try
{
var mbs = new ManagementObjectSearcher("Select SerialNumber From Win32_DiskDrive");
var mbsList = mbs.Get();
foreach (var mo in mbsList)
{
return mo["SerialNumber"].ToString();
}
}
catch { }
return string.Empty;
}
2. License Activation
public async Task<License> ActivateLicenseAsync(string activationKey, string email)
{
var hardwareId = GetHardwareId();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://xmis.azurewebsites.net/");
var request = new
{
ActivationKey = activationKey,
Email = email,
HardwareId = hardwareId,
ProductName = "YourProductName" // Replace with your product name
};
var response = await client.PostAsJsonAsync("api/license/activate", request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadFromJsonAsync<License>();
}
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Activation failed: {error}");
}
}
3. License Validation
public async Task<bool> ValidateLicenseAsync(string activationKey)
{
var hardwareId = GetHardwareId();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://xmis.azurewebsites.net/");
var request = new
{
ActivationKey = activationKey,
HardwareId = hardwareId
};
var response = await client.PostAsJsonAsync("api/license/validate", request);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ValidationResult>();
return result.IsValid;
}
return false;
}
}
4. License Deactivation
public async Task<bool> DeactivateLicenseAsync(string licenseKey, string productName, string hardwareId)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://xmis.azurewebsites.net/");
var request = new
{
LicenseKey = licenseKey,
ProductName = productName,
HardwareId = hardwareId
};
var response = await client.PostAsJsonAsync("api/license/deactivate", request);
if (response.IsSuccessStatusCode)
{
return true;
}
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Deactivation failed: {error}");
}
}
License Service Implementation
public class LicenseService
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://xmis.azurewebsites.net/";
public LicenseService()
{
_httpClient = new HttpClient
{
BaseAddress = new Uri(_baseUrl)
};
}
public async Task<License> ActivateLicenseAsync(string activationKey, string email)
{
var hardwareId = GetHardwareId();
var request = new
{
ActivationKey = activationKey,
Email = email,
HardwareId = hardwareId,
ProductName = "YourProductName" // Replace with your product name
};
var response = await _httpClient.PostAsJsonAsync("api/license/activate", request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadFromJsonAsync<License>();
}
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Activation failed: {error}");
}
public async Task<bool> ValidateLicenseAsync(string activationKey)
{
var hardwareId = GetHardwareId();
var request = new
{
ActivationKey = activationKey,
HardwareId = hardwareId
};
var response = await _httpClient.PostAsJsonAsync("api/license/validate", request);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ValidationResult>();
return result.IsValid;
}
return false;
}
public async Task<bool> DeactivateLicenseAsync(string licenseKey)
{
var request = new
{
LicenseKey = licenseKey,
ProductName = "YourProductName" // Replace with your product name
};
var response = await _httpClient.PostAsJsonAsync("api/license/deactivate", request);
if (response.IsSuccessStatusCode)
{
return true;
}
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Deactivation failed: {error}");
}
private static string GetHardwareId()
{
// Implementation as shown above
}
}
License Models
public class License
{
public string ActivationKey { get; set; }
public string Email { get; set; }
public string HardwareId { get; set; }
public DateTime? ActivationDate { get; set; }
public DateTime ExpirationDate { get; set; }
public bool IsActive { get; set; }
public string ProductName { get; set; }
public string Version { get; set; }
public string? Note { get; set; }
// Corporate licensing
public bool IsCorporate { get; set; }
public int? MaxSeats { get; set; }
public string? CorporateName { get; set; }
public int CurrentSeats { get; set; }
// Offline licensing
public bool IsOffline { get; set; }
public string? OfflineCode { get; set; }
}
public class ValidationResult
{
public bool IsValid { get; set; }
public string Status { get; set; }
public DateTime? ExpirationDate { get; set; }
public int[] Features { get; set; }
}
// Offline validation result (client-side)
public class OfflineValidationResult
{
public bool IsValid { get; set; }
public string? ErrorMessage { get; set; }
public string? ProductName { get; set; }
public int ProductId { get; set; }
public DateTime? ExpirationDate { get; set; }
public string? Email { get; set; }
public int[]? Features { get; set; }
public string? Version { get; set; }
public bool IsExpired { get; set; }
public bool HardwareMismatch { get; set; }
}
License History
public class LicenseHistory
{
public int Id { get; set; }
public string ActivationKey { get; set; }
public string Email { get; set; }
public string HardwareId { get; set; }
public string? IpAddress { get; set; }
public string? Country { get; set; }
public DateTime ActivationDate { get; set; }
public DateTime? DeactivationDate { get; set; }
public string Action { get; set; }
public string Notes { get; set; }
public DateTime CreatedAt { get; set; }
}
.NET Client SDK (NuGet)
Package: ExisOne.Client (version 0.4.0)
Install:
dotnet add package ExisOne.Client --version 0.4.0
Basic Usage (Online Validation)
var options = new ExisOneClientOptions { BaseUrl = "https://your-api/" };
var client = new ExisOneClient(options);
// Generate hardware ID
var hardwareId = client.GenerateHardwareId();
// Activate
await client.ActivateAsync(activationKey, email, hardwareId, productName);
// Validate
var isValid = await client.ValidateAsync(activationKey, hardwareId);
// Deactivate (requires same hardwareId)
var ok = await client.DeactivateAsync(activationKey, hardwareId, productName);
Offline License Validation
For customers without internet access, you can generate offline activation codes that are validated locally using RSA signatures.
Setup with Offline Public Key:
// Configure the client with the public key for offline validation
// Obtain this from your ExisOne dashboard under Crypto Keys
var client = new ExisOneClient(new ExisOneClientOptions
{
BaseUrl = "https://your-api.com",
OfflinePublicKey = @"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
-----END PUBLIC KEY-----"
});
Smart Validation (Auto-detects Online vs Offline):
// Generate hardware ID
var hardwareId = client.GenerateHardwareId();
// Smart validation - auto-detects online vs offline keys based on format
var result = await client.ValidateSmartAsync(licenseKey, hardwareId, "YourProductName");
if (result.IsValid)
{
Console.WriteLine($"Licensed until: {result.ExpirationDate}");
Console.WriteLine($"Was offline validation: {result.WasOffline}");
Console.WriteLine($"Features: {string.Join(",", result.Features ?? Array.Empty<int>())}");
}
else
{
Console.WriteLine($"Invalid: {result.ErrorMessage}");
}
Direct Offline Validation (No Network Calls):
// Validate offline code locally without any server connection
var offlineResult = client.ValidateOffline(offlineCode, hardwareId);
if (offlineResult.IsValid)
{
Console.WriteLine($"Product: {offlineResult.ProductName}");
Console.WriteLine($"Expires: {offlineResult.ExpirationDate}");
Console.WriteLine($"Features: {string.Join(",", offlineResult.Features ?? Array.Empty<int>())}");
}
else if (offlineResult.IsExpired)
{
Console.WriteLine("License has expired");
}
else if (offlineResult.HardwareMismatch)
{
Console.WriteLine("This license is bound to a different machine");
}
else
{
Console.WriteLine($"Invalid: {offlineResult.ErrorMessage}");
}
Deactivation with Opportunistic Sync:
// Deactivate license - tries to notify server but succeeds even if offline
var deactivateResult = await client.DeactivateSmartAsync(licenseKey, hardwareId, "YourProductName");
if (deactivateResult.Success)
{
Console.WriteLine($"Deactivated successfully");
Console.WriteLine($"Server notified: {deactivateResult.ServerNotified}");
if (!deactivateResult.ServerNotified)
{
Console.WriteLine("Note: Server will be notified when connection is restored");
}
}
Offline License Workflow
- Customer obtains Hardware ID: In your application, call
client.GenerateHardwareId()and display it to the user - Customer contacts support: They provide their hardware ID via email/phone
- Admin generates offline code: In the ExisOne dashboard, enter the hardware ID and generate an offline activation code
- Customer enters code: They paste the offline code into your application
- Local validation: Your app validates the code using
ValidateOffline()orValidateSmartAsync()
Offline Code Security Features
- RSA-SHA256 Signature: Codes are cryptographically signed and cannot be forged
- Hardware Binding: Each code is bound to a specific machine's hardware ID
- Embedded Data: Product ID, expiration date, email, and feature flags are all embedded
- Tamper Detection: Any modification invalidates the signature
- Expiration Enforcement: Validated against local machine time
Getting Started
- Clone the repository
- Update the connection string in
appsettings.json - Run the migrations:
dotnet ef database update - Run the application:
dotnet run
Admin Dashboard
Access the admin dashboard at /admin to manage products, licenses, and activation keys.
License
This project is licensed under the MIT License - see the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. 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 is compatible. 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 was computed. 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. |
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.