Designing a license gate: where to check, what to cache, and when to fail closed
Adding Keyright to a .NET app looks like one line: client.Validate() returns a LicenseInfo , you check IsValid , done. That line is the easy part, and it is not where licensing goes wrong. Licensing goes wrong in the architecture around that call — where it lives, how often it runs, what you keep between runs, and above all what happens when it cannot give you a clean answer. Get that wrong in…
In .NET applications, implementing licensing can be a straightforward one-liner: `client.Validate()` returns a `LicenseInfo`, and checking `IsValid` concludes the process. However, the complexity arises from the surrounding architecture - where the validation occurs, how frequently it runs, what data is retained between calls, and most importantly, what happens when the validation cannot provide a definitive answer.
This article outlines a licensing gate: a singular object responsible for the validation call, caching an immutable snapshot of the result, and making a deliberate decision on failure.
The first principle is that only one object should perform the SDK validation. Avoid scattering `Validate()` across view models, as this leads to re-verification on every button click, inconsistent responses when the clock nears expiry during a session, and a single point of policy alteration. Instead, validate once, store the outcome in a snapshot, and allow other parts of the app to read from this snapshot.
The `LicenseSnapshot` record is immutable, ensuring that once the gate distributes it, no feature can inadvertently modify the licensing state. A background refresh then swaps the reference atomically, rather than altering fields accessed by concurrent threads. The gate manages the `KeyrightClient`, the current snapshot, and the policy that translates raw `LicenseInfo` into a `LicenseSnapshot`:
The `LicenseGate` class includes methods for checking feature access based on the current snapshot's `AllowsPaid` status and entitlements, as well as checking feature limits. When the license needs refreshing, it attempts validation with the client. An exception signifies an unknown state, not a licensed one, and the gate defaults to the free edition.
An unresolved licensing state is never considered paid - this single principle encapsulates the entire post. The decision table interprets `LicenseInfo.Status`, which includes eight distinct outcomes, each requiring an explicit fail-open or fail-closed decision rather than an implicit boolean check.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.