Thin native Rust shim over the official magika crate for consumption from .NET 9 and .NET 10 through LibraryImport / P/Invoke.
dotnet add package Engagendy.Magika.NativePackage page:
https://www.nuget.org/packages/Engagendy.Magika.Native
Supported managed target frameworks:
net9.0net10.0
magika_session_newmagika_session_new_with_threadsmagika_session_freemagika_identify_path_jsonmagika_identify_bytes_jsonmagika_string_free
Both identify functions return a heap-allocated UTF-8 JSON string. The caller must free it with magika_string_free.
Success:
{
"ok": true,
"kind": "inferred",
"score": 0.997,
"info": {
"label": "python",
"mimeType": "text/x-python",
"group": "code",
"description": "Python source",
"extensions": ["py", "pyi"],
"isText": true
},
"contentType": {
"label": "python",
"mimeType": "text/x-python",
"group": "code",
"description": "Python source",
"extensions": ["py", "pyi"],
"isText": true
}
}Error:
{
"ok": false,
"error": "..."
}Rust is required.
Install Rust:
curl https://sh.rustup.rs -sSf | shBuild:
cargo build --releaseArtifacts:
- macOS:
target/release/libmagika_dotnet.dylib - Linux:
target/release/libmagika_dotnet.so - Windows:
target/release/magika_dotnet.dll
NuGet packages are built and published through GitHub Actions in .github/workflows/release.yml.
The published package version is derived from the Git tag, so tag v0.1.3 publishes NuGet package version 0.1.3.
Current published runtime targets:
linux-x64win-x64osx-arm64
osx-x64 is not currently shipped because the ONNX Runtime distribution used by ort-sys does not provide prebuilt x86_64-apple-darwin binaries.
Maintainer release instructions are in RELEASING.md.
The sample wrapper is under dotnet/Magika.Native. The NuGet package readme also includes install and usage examples directly on nuget.org.
Example:
using Magika.Native;
using var session = new MagikaSession();
string json = session.IdentifyPathJson("/path/to/file");
Console.WriteLine(json);For ASP.NET uploads, prefer IdentifyBytesJson when you already have the file in memory, and IdentifyPathJson when you save uploads to disk first.
If you do not want to work with raw JsonDocument, deserialize Magika's JSON into DTOs:
using System.Text.Json;
using System.Text.Json.Serialization;
using Magika.Native;
public sealed record MagikaResponse(
[property: JsonPropertyName("ok")] bool Ok,
[property: JsonPropertyName("kind")] string? Kind,
[property: JsonPropertyName("score")] double? Score,
[property: JsonPropertyName("info")] MagikaTypeInfo? Info,
[property: JsonPropertyName("contentType")] MagikaTypeInfo? ContentType,
[property: JsonPropertyName("error")] string? Error);
public sealed record MagikaTypeInfo(
[property: JsonPropertyName("label")] string? Label,
[property: JsonPropertyName("mimeType")] string? MimeType,
[property: JsonPropertyName("group")] string? Group,
[property: JsonPropertyName("description")] string? Description,
[property: JsonPropertyName("extensions")] string[]? Extensions,
[property: JsonPropertyName("isText")] bool? IsText);
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<MagikaSession>();
var app = builder.Build();
app.MapPost("/upload-typed", async (IFormFile file, MagikaSession magika) =>
{
if (file.Length == 0)
{
return Results.BadRequest("Empty file.");
}
await using var stream = file.OpenReadStream();
using var memory = new MemoryStream();
await stream.CopyToAsync(memory);
string json = magika.IdentifyBytesJson(memory.ToArray());
MagikaResponse? result = JsonSerializer.Deserialize<MagikaResponse>(json);
if (result is null)
{
return Results.Problem("Failed to deserialize Magika response.");
}
if (!result.Ok)
{
return Results.BadRequest(new { result.Error });
}
return Results.Ok(new
{
file.FileName,
result.Info?.Label,
result.Info?.MimeType,
result.Score
});
});
app.Run();This is the simplest path for a typical upload endpoint:
using System.Text.Json;
using Magika.Native;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<MagikaSession>();
var app = builder.Build();
app.MapPost("/upload", async (IFormFile file, MagikaSession magika) =>
{
if (file.Length == 0)
{
return Results.BadRequest("Empty file.");
}
await using var stream = file.OpenReadStream();
using var memory = new MemoryStream();
await stream.CopyToAsync(memory);
string json = magika.IdentifyBytesJson(memory.ToArray());
using JsonDocument document = JsonDocument.Parse(json);
bool ok = document.RootElement.GetProperty("ok").GetBoolean();
if (!ok)
{
string error = document.RootElement.GetProperty("error").GetString() ?? "Magika failed.";
return Results.BadRequest(new { error });
}
string? label = document.RootElement.GetProperty("info").GetProperty("label").GetString();
string? mimeType = document.RootElement.GetProperty("info").GetProperty("mimeType").GetString();
double score = document.RootElement.GetProperty("score").GetDouble();
return Results.Ok(new
{
file.FileName,
DetectedLabel = label,
MimeType = mimeType,
Score = score
});
});
app.Run();Use this when your upload flow already writes a temporary file:
Assume the same builder, app, and AddSingleton<MagikaSession>() setup from the previous example.
using System.Text.Json;
using Magika.Native;
app.MapPost("/upload-to-disk", async (IFormFile file, MagikaSession magika) =>
{
if (file.Length == 0)
{
return Results.BadRequest("Empty file.");
}
string tempPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}_{file.FileName}");
try
{
await using (var output = File.Create(tempPath))
{
await file.CopyToAsync(output);
}
string json = magika.IdentifyPathJson(tempPath);
using JsonDocument document = JsonDocument.Parse(json);
return Results.Ok(document.RootElement.Clone());
}
finally
{
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
}
});- Register
MagikaSessionas a singleton and reuse it across requests instead of creating a new session for every upload. - Use the reported
mimeType,label, andscoreas validation inputs, not the browser-suppliedContentTypealone. - Keep normal upload limits in place. Magika helps identify content, but it is not a replacement for size limits, antivirus scanning, or authorization checks.
For NuGet packaging, place the native library under RID-specific paths such as:
runtimes/osx-arm64/native/libmagika_dotnet.dylibruntimes/linux-x64/native/libmagika_dotnet.soruntimes/win-x64/native/magika_dotnet.dll