Documentation · SoftActivate Licensing

Quickstart: .NET (C# / VB.NET)

This quickstart takes about ten minutes. You activate a license from .NET, persist it to disk, confirm it validates with no network connection, and release the seat again. C# and VB.NET consume the same assembly - SWS.dll, imported as SWS.Licensing - and every example on this page is shown in both languages.

Before you start

From Getting started you should have your vendor id (console → Settings), a license key (issued by an order, Orders → Create, and emailed to the customer), and the NuGet package:

dotnet add package SoftActivate.Sdk

The whole program

This is a complete, runnable integration: the same loop your application runs on every start.

C#

using System;
using System.IO;
using System.Threading.Tasks;
using SWS.Licensing;

internal static class Program
{
    private static async Task<int> Main()
    {
        using var client = new LicensingClient("https://lm.sws.softactivate.com")
        {
            // your tenant coordinates - vendor id from the console (Settings),
            // license key from your order's delivery email
            VendorId = "YOUR-VENDOR-ID",
            LicenseKey = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
            TrustedDomain = "sws.softactivate.com"
        };

        // your product's Reference Id (console -> Products): a key for a
        // different product is refused - see the product binding guide
        client.SetProductId("YOUR_PRODUCT_ID");

        // after the first (online) activation, every later start validates the
        // stored license file with no network access at all
        License? existing = File.Exists("license.dat")
            ? License.Parse(File.ReadAllText("license.dat"))
            : null;

        // with no stored license this activates the device (online, once); with one
        // it verifies the chain, the device fingerprint and the dates locally -
        // and renews online only when needed
        LicenseValidationResult result = await client.ValidateLicenseAsync(existing);

        if (!result.IsValid)
        {
            // the precise status says WHY - expired, revoked, bound to another
            // machine, subscription lapsed... each deserves different UX; see the
            // statuses reference: /licensing/docs/status-codes/
            Console.Error.WriteLine($"License is not valid: {result.Status}");

            // when an activation failed, Error says why (HTTP status, error code,
            // message) - log it, it is what support needs
            if (result.Error != null)
                Console.Error.WriteLine($"Activation error: {result.Error}");

            return 1;
        }

        // persist the new or renewed certificate whenever one is issued
        if (result.UpdatedLicense != null)
            File.WriteAllText("license.dat", result.UpdatedLicense.Serialize());

        License current = result.UpdatedLicense ?? existing!;
        Console.WriteLine(current.ExpirationDate == License.NoExpiration
            ? "License is valid, with no expiration."
            : $"License is valid until {current.ExpirationDate:u}.");
        return 0;
    }
}

VB.NET

Imports System
Imports System.IO
Imports System.Threading.Tasks
Imports SWS.Licensing

Module Program
    Function Main() As Integer
        Return MainAsync().GetAwaiter().GetResult()
    End Function

    Private Async Function MainAsync() As Task(Of Integer)
        ' your tenant coordinates - vendor id from the console (Settings),
        ' license key from your order's delivery email
        Using client As New LicensingClient("https://lm.sws.softactivate.com") With {
            .VendorId = "YOUR-VENDOR-ID",
            .LicenseKey = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
            .TrustedDomain = "sws.softactivate.com"
        }
            ' your product's Reference Id (console -> Products): a key for a
            ' different product is refused - see the product binding guide
            client.SetProductId("YOUR_PRODUCT_ID")

            ' after the first (online) activation, every later start validates the
            ' stored license file with no network access at all
            Dim existing As License = Nothing
            If File.Exists("license.dat") Then
                existing = License.Parse(File.ReadAllText("license.dat"))
            End If

            ' with no stored license this activates the device (online, once); with
            ' one it verifies the chain, the device fingerprint and the dates
            ' locally - and renews online only when needed
            Dim result As LicenseValidationResult = Await client.ValidateLicenseAsync(existing)

            If Not result.IsValid Then
                ' the precise status says WHY - expired, revoked, bound to another
                ' machine, subscription lapsed... each deserves different UX; see
                ' the statuses reference: /licensing/docs/status-codes/
                Console.Error.WriteLine($"License is not valid: {result.Status}")

                ' when an activation failed, Error says why (HTTP status, error
                ' code, message) - log it, it is what support needs
                If result.Error IsNot Nothing Then
                    Console.Error.WriteLine($"Activation error: {result.Error}")
                End If

                Return 1
            End If

            ' persist the new or renewed certificate whenever one is issued
            If result.UpdatedLicense IsNot Nothing Then
                File.WriteAllText("license.dat", result.UpdatedLicense.Serialize())
            End If

            Dim current As License = If(result.UpdatedLicense, existing)
            If current.ExpirationDate = License.NoExpiration Then
                Console.WriteLine("License is valid, with no expiration.")
            Else
                Console.WriteLine($"License is valid until {current.ExpirationDate:u}.")
            End If
            Return 0
        End Using
    End Function
End Module

Replace the vendor id and license key with yours, put deployment-root.crt next to the binary (in your real product, embed it as a resource or install it with your application), and run it.

Run it three times

  1. First run (online): no license.dat exists, so ValidateLicenseAsync activates the device with the service. The SDK receives the license and the program saves it to license.dat.
  2. Second run (disconnect the network if you like): the stored license is verified entirely on the machine, and no network access happens. This is the everyday path your customers' machines take on every start.
  3. Moving on: when you are done with this machine, release its seat so the key can activate elsewhere:
await client.DeactivateLicenseAsync();
File.Delete("license.dat"); // the local file is now meaningless
Await client.DeactivateLicenseAsync()
File.Delete("license.dat") ' the local file is now meaningless

When validation says no

A false result.IsValid can mean several different things. result.Status tells you whether the license is expired, revoked, bound to a different device (BadContext), or waiting on a lapsed subscription (PaymentRequired, with the subscription state in result.StatusMessage), and each case deserves different UX. When the online activation itself failed, result.Error says why: the HTTP status, the service's error code (for example license_not_found or vendor_mismatch) and its message. Log it. The statuses and errors reference maps every status to a recommended action.

Publishing your app

Every way of publishing a .NET 8 app works with the SDK as of version 1.1.0, and with SDK 1.2.0 and later a Native AOT app recognizes the machine as reliably as a regular build:

  • Framework-dependent, self-contained, and single file need nothing special.
  • Trimmed (PublishTrimmed): the NuGet package configures the trimmer for you, so device identification works fully. If you reference SWS.dll directly instead of the package, add the same settings to your project file yourself:
<PropertyGroup>
  <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
</PropertyGroup>
<ItemGroup>
  <TrimmerRootAssembly Include="System.Management" />
</ItemGroup>
  • Native AOT (PublishAot): supported, with a slightly weaker device binding on Windows, which the build notes with warning SWS0001. Since SDK 1.2.0, the same machine has the same identity in a Native AOT build and in a regular build. SDK 1.1.0 cannot identify some Windows machines reliably under Native AOT, and their activation is refused (device_unidentifiable, reported as NotFound; see hardware IDs). Upgrade to SDK 1.2.0 or later, or publish trial builds without Native AOT.

SDK 1.0.0 cannot activate from a trimmed or Native AOT app: it fails before the request is sent and reports NotFound. Upgrade to 1.1.0. If you can't upgrade yet, publish without trimming, or keep trimming and add all four settings, then rebuild from clean:

<PropertyGroup>
  <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
  <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
</PropertyGroup>
<ItemGroup>
  <TrimmerRootAssembly Include="SWS" />
  <TrimmerRootAssembly Include="System.Management" />
</ItemGroup>

Next: the C++ quickstart, or the statuses and errors reference.