ExisOne.Client 0.7.0

dotnet add package ExisOne.Client --version 0.7.0
                    
NuGet\Install-Package ExisOne.Client -Version 0.7.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ExisOne.Client" Version="0.7.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ExisOne.Client" Version="0.7.0" />
                    
Directory.Packages.props
<PackageReference Include="ExisOne.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ExisOne.Client --version 0.7.0
                    
#r "nuget: ExisOne.Client, 0.7.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ExisOne.Client@0.7.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ExisOne.Client&version=0.7.0
                    
Install as a Cake Addin
#tool nuget:?package=ExisOne.Client&version=0.7.0
                    
Install as a Cake Tool

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 system
  • SoftwareActivationSystem.Core: Core library containing shared interfaces and models
  • SoftwareActivationSystem.HardwareId: Library for generating unique hardware IDs

Setup

  1. Update the database connection string in appsettings.json with your Azure SQL Database credentials:
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=exiswwwdb.database.windows.net;Database=exiswwwdb;User Id=your_username;Password=your_password;"
  }
}
  1. Build and run the solution:
dotnet build
dotnet run --project SoftwareActivationSystem.Api
  1. Access the web interface at https://localhost:5001 to generate activation keys.

API Endpoints

Products

  • GET /api/products - Get all products
  • GET /api/products/{id} - Get a specific product
  • POST /api/products - Create a new product
  • PUT /api/products/{id} - Update a product
  • DELETE /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 (when hardwareId is 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 licenses
  • POST /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

  1. Customer obtains Hardware ID: In your application, call client.GenerateHardwareId() and display it to the user
  2. Customer contacts support: They provide their hardware ID via email/phone
  3. Admin generates offline code: In the ExisOne dashboard, enter the hardware ID and generate an offline activation code
  4. Customer enters code: They paste the offline code into your application
  5. Local validation: Your app validates the code using ValidateOffline() or ValidateSmartAsync()

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

  1. Clone the repository
  2. Update the connection string in appsettings.json
  3. Run the migrations: dotnet ef database update
  4. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
0.7.0 102 1/1/2026
0.6.0 93 12/29/2025
0.5.0 98 12/28/2025
0.4.0 119 12/26/2025
0.3.0 176 12/22/2025
0.2.0 194 10/9/2025
0.1.1 187 10/8/2025
0.1.0 197 10/8/2025