IDEncoder.AspNetCore
0.5.0
See the version list below for details.
dotnet add package IDEncoder.AspNetCore --version 0.5.0
NuGet\Install-Package IDEncoder.AspNetCore -Version 0.5.0
<PackageReference Include="IDEncoder.AspNetCore" Version="0.5.0" />
<PackageVersion Include="IDEncoder.AspNetCore" Version="0.5.0" />
<PackageReference Include="IDEncoder.AspNetCore" />
paket add IDEncoder.AspNetCore --version 0.5.0
#r "nuget: IDEncoder.AspNetCore, 0.5.0"
#:package IDEncoder.AspNetCore@0.5.0
#addin nuget:?package=IDEncoder.AspNetCore&version=0.5.0
#tool nuget:?package=IDEncoder.AspNetCore&version=0.5.0
IDEncoder
A .NET library that encodes numeric IDs (long) into short, URL-safe Base62 strings — similar to how YouTube, Twitter and other services expose public IDs like dQw4w9WgXcQ instead of raw database numbers.
Uses Blowfish encryption under the hood with no external cryptography dependencies — the cipher is implemented from scratch.
Database: 42
URL: /video/xK9mQ3bPl2a
JSON: { "id": "xK9mQ3bPl2a", "title": "..." }
Why
- Hide sequential IDs — users can't guess or enumerate resource URLs
- Short output — fixed 11 characters with the default Blowfish codec (vs 20 digits for
long.MaxValue); 2–12 characters with the Sqids-style codec - Reversible — decode back to the original number with the same key
- Salted encoding — same ID produces different strings for different entity types
- Zero external crypto dependencies — ships its own Blowfish implementation
Install
dotnet add package IDEncoder # core: encoding, EncodedId, JSON, DI
dotnet add package IDEncoder.AspNetCore # + MVC model binding and JSON wiring
The core package has no ASP.NET Core dependency — it works in console apps, workers and
plain dotnet/runtime containers. Add IDEncoder.AspNetCore only in web apps.
Quick start
var encoder = new IDEncoder.IDEncoder("my-secret-key");
string encoded = encoder.Encode(42); // "xK9mQ3bPl2a"
long decoded = encoder.Decode(encoded); // 42
Salt
By default, the same numeric ID always encodes to the same string. This means a user who knows a video ID could try it as a gallery ID, a user ID, etc.
Salt solves this — the same number produces different strings for different entity types:
encoder.Encode(42); // "xK9mQ3bPl2a"
encoder.Encode(42, "video"); // "t7RqN1cWm5z"
encoder.Encode(42, "gallery"); // "p3KmW8vLn9a"
// Decoding requires the same salt
encoder.Decode("t7RqN1cWm5z", "video"); // 42
encoder.Decode("p3KmW8vLn9a", "gallery"); // 42
Salted ciphers are cached internally — no performance penalty for reusing the same salt.
Codecs
IDEncoder is a facade over an IIdCodec. Two are built in:
BlowfishIdCodec(default) — Blowfish encryption, fixed 11-char output, hides sequential IDs.new IDEncoder("secret")uses this.SqidsIdCodec— simple, non-secure, Sqids-style: short variable-length strings where sequential IDs look dissimilar, with no secret key. Obfuscation only, not cryptography.
services.AddIDEncoder(new SqidsIdCodec()); // simple codec
services.AddIDEncoder(new BlowfishIdCodec("key")); // explicit Blowfish
Custom codecs
Implement IIdCodec, or derive from IdCodec to get salt caching for free, and register it:
public sealed class MyCodec : IdCodec {
public override int MaxEncodedLength => 16;
public override string Encode(long id) => /* ... */;
public override int Encode(long id, Span<char> destination) => /* ... */;
public override long Decode(ReadOnlySpan<char> encoded) => /* ... */;
protected override IIdCodec CreateWithSalt(string salt) => /* salted variation from base */;
}
services.AddIDEncoder(new MyCodec());
Codec output is not self-describing. Don't switch codecs over data that already has encoded IDs in the wild — a string from one codec won't decode correctly with another.
ASP.NET Core integration
Registration
// Key known at startup
services.AddIDEncoder("my-secret-key");
// Key from configuration
services.AddIDEncoder(provider => {
var config = provider.GetRequiredService<IConfiguration>();
return config["IDEncoder:SecretKey"]!;
});
// Key loaded later (e.g. from database after startup)
services.AddIDEncoderProvider();
Then wire MVC (IDEncoder.AspNetCore package):
services.AddControllers(o => o.UseIDEncoderModelBinding()) // EncodedId route binding + [Salt]
.AddIDEncoderJson(); // EncodedId JSON + [Salt] via DI
AddIDEncoderJson() binds JSON serialization to the encoder registered in DI (any of the
three registration styles above, including the deferred provider). The manual
AddJsonOptions(o => o.JsonSerializerOptions.UseIDEncoderSalts()) setup still works and
uses the process-wide ambient encoder instead.
For deferred initialization, call Configure when the key becomes available:
public class AppInitializer(IDEncoderProvider encoderProvider, IMyConfigService config) {
public async Task InitializeAsync() {
string secret = await config.GetSecretAsync("ID_ENCODER_SECRET");
encoderProvider.Configure(secret);
}
}
EncodedId struct
A long wrapper that automatically encodes/decodes at JSON and route-binding boundaries:
// DTO — JSON output is a Base62 string
public record ChatResult(EncodedId Id, string Title);
// Mapping — implicit conversion from long
return new ChatResult(Id: chat.Id, Title: chat.Title);
// { "id": "xK9mQ3bPl2a", "title": "My chat" }
// Controller — parsed from URL automatically via IParsable
[HttpGet("{id}")]
public async Task<IActionResult> Get(EncodedId id) {
long dbId = id; // implicit conversion to long
var chat = await db.Chats.FindAsync(dbId);
return Ok(new ChatResult(chat.Id, chat.Title));
}
The database stores a plain long. Encoding only happens at the JSON/URL boundary.
Salt via attribute
Use [Salt("...")] on properties to automatically apply per-entity-type salt during JSON serialization:
public record VideoResult(
[property: Salt("video")] EncodedId Id,
string Title
);
public record GalleryResult(
[property: Salt("gallery")] EncodedId Id,
string Title
);
// Same DB id = 42, but different JSON output:
// VideoResult: { "id": "t7RqN1cWm5z", "title": "..." }
// GalleryResult: { "id": "p3KmW8vLn9a", "title": "..." }
Salt support in JSON and route binding is enabled by the standard wiring:
services.AddControllers(o => o.UseIDEncoderModelBinding())
.AddIDEncoderJson();
Salt also works on controller parameters for route/query binding:
[HttpPost("{id}/test")]
public async Task<IActionResult> TestConnection([Salt("s3")] EncodedId id) {
long dbId = id; // correctly decoded with "s3" salt
}
JSON behavior
| Input | Output |
|---|---|
Write(42) |
"xK9mQ3bPl2a" |
Read("xK9mQ3bPl2a") |
42 |
Read(42) |
rejected with a 400 by default |
Raw JSON numbers are rejected by default: if they were accepted, any endpoint binding an
EncodedId from a request body would let clients address resources by plain sequential
numbers ({"id": 1}, {"id": 2}, …), bypassing the encoding entirely. If you are migrating
clients that still send numbers, enable it explicitly and consciously:
.AddIDEncoderJson(allowNumericInput: true)
// or: o.JsonSerializerOptions.UseIDEncoder(encoder, allowNumericInput: true)
Security notes
- Encoding hides IDs; it does not authorize. Nearly any well-formed 11-character Base62
string decodes to some
long— decode success does not prove the caller was ever given that ID. Always perform existence and authorization checks after decoding. - Blowfish encoding is deterministic: the same ID with the same key and salt always produces the same string. Use salts to stop IDs from being tried across entity types.
SqidsIdCodecis obfuscation only — anyone can decode its output without a key.
Zero-alloc API
For high-throughput scenarios, span-based overloads avoid heap allocations:
// Encode into a stack buffer sized for the active codec
Span<char> buffer = stackalloc char[encoder.MaxEncodedLength];
int n = encoder.Encode(42, buffer);
// Decode from the written slice (works for fixed- and variable-length codecs)
long id = encoder.Decode(buffer[..n]);
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- IDEncoder (>= 0.5.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.