What I learned building an xAPI 2.0 library and LRS for .NET
The author built xapi-dotnet, an xAPI 2.0 (IEEE 9274.1.1) library with statement models, an async client, a durable outbox and an LRS that runs inside ASP.NET Core. The write-up covers conformance testing in CI, reflection-free JSON handling for Native AOT and Unity, and the real cost of durable writes. Useful for any team sending e-learning data from .NET.
Key points
- Packages: Xapi.Core, Xapi.Client, Xapi.Client.Outbox, and Xapi.Lrs for ASP.NET Core; version 0.2.0 is on NuGet.
- The conformance suite exit code wraps at 256 failures, so the author's CI parses the JSON log instead.
- The outbox gives at-least-once delivery; the UUIDv7 statement id stays the same on resend, so duplicates are safe.
- On macOS, FileStream.Flush(true) costs about 4.7 ms per enqueue; avoid awaiting each enqueue on hot paths.
- On PostgreSQL, writes use SERIALIZABLE and retry on SQLSTATE 40001.
- The API can still change before 1.0, and the repo includes a migration guide for TinCan.NET users.
The author released xapi-dotnet 0.2.0, a .NET implementation of xAPI 2.0 (IEEE 9274.1.1). The package includes statement models with validation, an async LRS client, a durable outbox, and an LRS that can be embedded in an ASP.NET Core app on SQLite or PostgreSQL. It is meant to replace TinCan.NET, which has not changed since 2018 and does not support 2.0.
The write-up is a set of engineering lessons. CI reads the conformance suite's JSON log because its exit code wraps at 256 failures. Statements are handled with Utf8JsonReader and Utf8JsonWriter so they work with Native AOT, trimming and Unity IL2CPP. The outbox assigns UUIDv7 ids so resends are safe, but its file flush costs about 4.7 ms per enqueue on macOS. PostgreSQL writes run under SERIALIZABLE with retries.
Real-world use
- An e-learning platform backend that sends course completion and score statements to an LRS from .NET.
- A mobile or desktop training app that must keep statements across network loss using a durable outbox.
- A small training deployment that hosts its own LRS inside an ASP.NET Core app with SQLite storage.
- A Unity game or simulation, including WebGL builds, that reports learner progress to an LRS.
Why it matters
If your .NET app sends xAPI data, TinCan.NET is no longer the only option. The lessons on at-least-once delivery, idempotent resends and SERIALIZABLE retries also apply to any outbox or write-heavy data layer you design.