CLR Bindings and Interop
Tsonic does not hide CLR interop behind a JS-like illusion. It keeps it explicit and package-based.
Import CLR APIs explicitly
import { Console } from "@tsonic/dotnet/System.js";
import { Enumerable } from "@tsonic/dotnet/System.Linq.js";
export function main(): void {
const xs = [1, 2, 3];
const filtered = Enumerable.Where(xs, (x: number): boolean => x > 1);
Console.WriteLine(filtered.Count().ToString());
}
Where these packages come from
Generated CLR binding packages are produced by tsbindgen.
Examples:
@tsonic/dotnet@tsonic/aspnetcore@tsonic/microsoft-extensions@tsonic/efcore
Add external CLR dependencies
Tsonic supports three main CLR input paths.
Framework references
tsonic add framework Microsoft.AspNetCore.App @tsonic/aspnetcore
Use this for shared frameworks such as ASP.NET Core.
NuGet packages
tsonic add nuget Microsoft.EntityFrameworkCore 10.0.0
tsonic restore
Use this for package references that should live in tsonic.workspace.json.
The corresponding Tsonic binding package is then imported explicitly. Example:
import { DbContext } from "@tsonic/efcore/Microsoft.EntityFrameworkCore.js";
Local DLLs
tsonic add package ./libs/MyCompany.MyLib.dll
If you do not provide a types package explicitly, Tsonic can generate one with
tsbindgen.
Typical interop patterns
BCL usage
import { Console } from "@tsonic/dotnet/System.js";
import { List } from "@tsonic/dotnet/System.Collections.Generic.js";
Broad CLR object values
Generated CLR binding packages represent System.Object as TypeScript
unknown. A broad object value can be passed to another broad CLR slot, stored,
or narrowed through a proven API. It cannot be used for arbitrary member access.
Value-type constraints are represented as NonNullable<unknown>, which keeps
struct-like generic constraints distinct from unconstrained object slots.
ASP.NET Core usage
Install the framework binding package explicitly:
tsonic add framework Microsoft.AspNetCore.App @tsonic/aspnetcore
tsonic restore
import { WebApplication } from "@tsonic/aspnetcore/Microsoft.AspNetCore.Builder.js";
import type { ExtensionMethods } from "@tsonic/aspnetcore/Microsoft.AspNetCore.Builder.js";
export function main(): void {
const builder = WebApplication.CreateBuilder();
const app = builder.Build() as ExtensionMethods<WebApplication>;
app.MapGet("/", () => "Hello from ASP.NET Core");
app.Run("http://localhost:8080");
}
EF Core usage
Add the CLR package you need, then use the matching generated binding package:
tsonic add nuget Microsoft.EntityFrameworkCore.Sqlite 10.0.0
tsonic add npm @tsonic/efcore
tsonic add npm @tsonic/efcore-sqlite
tsonic restore
import {
DbContext,
DbSet,
} from "@tsonic/efcore/Microsoft.EntityFrameworkCore.js";
import { SqliteDbContextOptionsBuilderExtensions } from "@tsonic/efcore-sqlite/Microsoft.EntityFrameworkCore.js";
export class TodoItem {
id: number = 0;
title: string = "";
}
export class AppDbContext extends DbContext {
todos!: DbSet<TodoItem>;
}
export function createContext(): AppDbContext {
const builder = AppDbContext.CreateOptionsBuilder();
SqliteDbContextOptionsBuilderExtensions.UseSqlite(
builder,
"Data Source=app.db"
);
return new AppDbContext(builder.Options);
}
What the generated package docs should cover
Generated binding packages like @tsonic/aspnetcore and @tsonic/efcore*
should be documented in terms of:
- how to add the framework or NuGet dependency
- which generated npm package to install
- how to import the resulting CLR namespaces
- minimal working examples
They are not first-party authored source packages, so the docs do not try to restate every API member in those repos individually.
Why the docs separate this from first-party packages
The architecture has two distinct kinds of packages:
- authored first-party source packages like
@tsonic/js,@tsonic/nodejs, and@tsonic/express - generated CLR binding packages produced by
tsbindgen
The site keeps those categories separate because they have different build, ownership, and release flows.