A .NET library to download files (ONNX models, tokenizers, voice presets, etc.) from Hugging Face Hub repositories with progress reporting, caching, and authentication support.
C#
9
29 commits
updated Sep 28, 2026
A .NET library and CLI tool to download files (ONNX models, tokenizers, voice presets, etc.) from Hugging Face Hub repositories with progress reporting, caching, and authentication support.
HF_ENDPOINT or downloader optionsIServiceCollection extension methods| Package | Version | Downloads | Description |
|---|---|---|---|
| ElBruno.HuggingFace.Downloader | Core library for downloading files from Hugging Face Hub repositories | ||
| ElBruno.HuggingFace.Downloader.Cli | CLI tool (hfdownload) for managing Hugging Face downloads |
dotnet add package ElBruno.HuggingFace.Downloader
dotnet tool install -g ElBruno.HuggingFace.Downloader.Cli
Once installed, use the hfdownload command:
# Download model files
hfdownload download sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx tokenizer.json
# Download a specific tag into a revision-aware cache directory
hfdownload download sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx --revision v1.0
# Restart from scratch instead of resuming a preserved partial download
hfdownload download sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx --no-resume
# Ensure a manifest-defined bundle with integrity validation
hfdownload download --manifest ./phi4-bundle.json -o ./models/phi4
# Check if files exist locally
hfdownload check sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx tokenizer.json
# List remote files in a repo without knowing filenames up front
hfdownload files sentence-transformers/all-MiniLM-L6-v2
# List remote files in a dataset repo, as JSON
hfdownload files my-org/my-dataset --repo-type dataset --format json
# List cached models
hfdownload list
# Delete a cached model
hfdownload delete sentence-transformers/all-MiniLM-L6-v2
# See all commands
hfdownload --help
See the full CLI Reference for all commands and options.
using ElBruno.HuggingFace;
using var downloader = new HuggingFaceDownloader();
var request = new DownloadRequest
{
RepoId = "sentence-transformers/all-MiniLM-L6-v2",
LocalDirectory = "./models/miniLM",
RequiredFiles = ["onnx/model.onnx", "tokenizer.json"],
OptionalFiles = ["tokenizer_config.json", "vocab.txt"],
Revision = "refs/pr/42"
};
await downloader.DownloadFilesAsync(request);
Console.WriteLine($"Resolved commit: {request.ResolvedCommitSha}");
var manifest = new ModelBundleManifest
{
RepoId = "microsoft/Phi-4-mini-instruct-onnx",
Revision = "main",
Files =
[
new ModelBundleFile
{
Path = "onnx/model.onnx",
Size = 123456789,
Sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
},
new ModelBundleFile
{
Path = "tokenizer.json",
Required = false
}
]
};
var bundle = await downloader.EnsureBundleAsync(manifest, "./models/phi4");
Console.WriteLine(bundle.ResolvedManifestPath);
Manifest-driven bundle downloads validate existing files before reuse, re-download corrupted files, and write a resolved manifest to ./models/phi4/.hf.bundle.resolved.json.
var request = new DownloadRequest
{
RepoId = "my-org/my-model",
LocalDirectory = "./models/pinned",
RequiredFiles = ["model.onnx"],
Revision = "main",
ExpectedCommitSha = "1234567890abcdef1234567890abcdef12345678"
};
await downloader.DownloadFilesAsync(request);
Console.WriteLine(request.ResolvedCommitSha);
bool ready = downloader.AreFilesAvailable(
["onnx/model.onnx", "tokenizer.json"],
"./models/miniLM");
if (!ready)
{
var missing = downloader.GetMissingFiles(
["onnx/model.onnx", "tokenizer.json"],
"./models/miniLM");
Console.WriteLine($"Missing {missing.Count} files");
}
IReadOnlyList<RepoTreeEntry> entries = await downloader.ListRepoFilesAsync(
"sentence-transformers/all-MiniLM-L6-v2",
RepoType.Model);
foreach (var entry in entries.Where(e => !e.IsDirectory))
Console.WriteLine($"{entry.Path} ({entry.SizeBytes} bytes)");
// Use the discovered paths directly as a download request
await downloader.DownloadFilesAsync(new DownloadRequest
{
RepoId = "sentence-transformers/all-MiniLM-L6-v2",
LocalDirectory = "./models/miniLM",
RequiredFiles = [.. entries.Where(e => !e.IsDirectory).Select(e => e.Path)]
});
var progress = new Progress<DownloadProgress>(p =>
{
if (p.Stage == DownloadStage.Downloading)
Console.Write($"\rβ¬οΈ [{p.CurrentFile}] {p.PercentComplete:F0}% (reused {ByteFormatHelper.FormatBytes(p.ResumedBytes)})");
else
Console.WriteLine($"{p.Stage}: {p.Message}");
});
await downloader.DownloadFilesAsync(new DownloadRequest
{
RepoId = "sentence-transformers/all-MiniLM-L6-v2",
LocalDirectory = "./models/miniLM",
RequiredFiles = ["onnx/model.onnx", "tokenizer.json"],
Progress = progress
});
Set the HF_TOKEN environment variable, or pass it explicitly:
var downloader = new HuggingFaceDownloader(new HuggingFaceDownloaderOptions
{
AuthToken = "hf_your_token_here"
});
Set the HF_ENDPOINT environment variable, or configure the endpoint explicitly:
var downloader = new HuggingFaceDownloader(new HuggingFaceDownloaderOptions
{
Endpoint = "https://huggingface.internal"
});
The explicit Endpoint takes precedence over HF_ENDPOINT; if neither is set, the downloader uses https://huggingface.co.
builder.Services.AddHuggingFaceDownloader(options =>
{
options.Timeout = TimeSpan.FromMinutes(60);
});
// Then inject HuggingFaceDownloader in your services
public class MyModelService(HuggingFaceDownloader downloader)
{
public async Task EnsureModelAsync()
{
await downloader.DownloadFilesAsync(new DownloadRequest
{
RepoId = "my-org/my-model",
LocalDirectory = DefaultPathHelper.GetDefaultCacheDirectory("MyApp"),
RequiredFiles = ["model.onnx", "tokenizer.json"]
});
}
}
var cacheRoot = DefaultPathHelper.GetDefaultCacheDirectory("hfdownload");
var repoId = "sentence-transformers/all-MiniLM-L6-v2";
// List all cached repositories
var cachedRepos = downloader.ListCachedRepos(cacheRoot);
// Check whether required files are cached for one repo
bool isCached = downloader.IsCached(
repoId,
cacheRoot,
["onnx/model.onnx", "tokenizer.json"]);
// Get total cache size for one local repo directory
long size = downloader.GetCachedSize(
Path.Combine(cacheRoot, DefaultPathHelper.SanitizeModelName(repoId)));
// Delete cached files for the repo (safe no-op if not found)
await downloader.DeleteCachedFilesAsync(repoId, cacheRoot);
Direct downloads also write ./models/.../.hf.download.resolved.json so callers can inspect the requested revision and resolved commit later.
| Topic | Description |
|---|---|
| Getting Started | Installation, all usage examples, and setup |
| CLI Reference | Complete CLI command reference |
| API Reference | Complete class and method documentation |
| Architecture | Design decisions, data flow, and project structure |
| Publishing | NuGet publishing with GitHub Actions |
git clone https://github.com/elbruno/ElBruno.HuggingFace.Downloader.git
cd ElBruno.HuggingFace.Downloader
dotnet build ElBruno.HuggingFace.Downloader.slnx
dotnet test ElBruno.HuggingFace.Downloader.slnx
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License β see the LICENSE file for details.
Hi! I'm ElBruno π§‘, a passionate developer and content creator exploring AI, .NET, and modern development practices.
Made with β€οΈ by ElBruno
If you like this project, consider following my work across platforms:
C#
100.0%
A .NET library to download files (ONNX models, tokenizers, voice presets, etc.) from Hugging Face Hub repositories with progress reporting, caching, and authentication support.
C#
9
29 commits
updated Sep 28, 2026
A .NET library and CLI tool to download files (ONNX models, tokenizers, voice presets, etc.) from Hugging Face Hub repositories with progress reporting, caching, and authentication support.
HF_ENDPOINT or downloader optionsIServiceCollection extension methods| Package | Version | Downloads | Description |
|---|---|---|---|
| ElBruno.HuggingFace.Downloader | Core library for downloading files from Hugging Face Hub repositories | ||
| ElBruno.HuggingFace.Downloader.Cli | CLI tool (hfdownload) for managing Hugging Face downloads |
dotnet add package ElBruno.HuggingFace.Downloader
dotnet tool install -g ElBruno.HuggingFace.Downloader.Cli
Once installed, use the hfdownload command:
# Download model files
hfdownload download sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx tokenizer.json
# Download a specific tag into a revision-aware cache directory
hfdownload download sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx --revision v1.0
# Restart from scratch instead of resuming a preserved partial download
hfdownload download sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx --no-resume
# Ensure a manifest-defined bundle with integrity validation
hfdownload download --manifest ./phi4-bundle.json -o ./models/phi4
# Check if files exist locally
hfdownload check sentence-transformers/all-MiniLM-L6-v2 onnx/model.onnx tokenizer.json
# List remote files in a repo without knowing filenames up front
hfdownload files sentence-transformers/all-MiniLM-L6-v2
# List remote files in a dataset repo, as JSON
hfdownload files my-org/my-dataset --repo-type dataset --format json
# List cached models
hfdownload list
# Delete a cached model
hfdownload delete sentence-transformers/all-MiniLM-L6-v2
# See all commands
hfdownload --help
See the full CLI Reference for all commands and options.
using ElBruno.HuggingFace;
using var downloader = new HuggingFaceDownloader();
var request = new DownloadRequest
{
RepoId = "sentence-transformers/all-MiniLM-L6-v2",
LocalDirectory = "./models/miniLM",
RequiredFiles = ["onnx/model.onnx", "tokenizer.json"],
OptionalFiles = ["tokenizer_config.json", "vocab.txt"],
Revision = "refs/pr/42"
};
await downloader.DownloadFilesAsync(request);
Console.WriteLine($"Resolved commit: {request.ResolvedCommitSha}");
var manifest = new ModelBundleManifest
{
RepoId = "microsoft/Phi-4-mini-instruct-onnx",
Revision = "main",
Files =
[
new ModelBundleFile
{
Path = "onnx/model.onnx",
Size = 123456789,
Sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
},
new ModelBundleFile
{
Path = "tokenizer.json",
Required = false
}
]
};
var bundle = await downloader.EnsureBundleAsync(manifest, "./models/phi4");
Console.WriteLine(bundle.ResolvedManifestPath);
Manifest-driven bundle downloads validate existing files before reuse, re-download corrupted files, and write a resolved manifest to ./models/phi4/.hf.bundle.resolved.json.
var request = new DownloadRequest
{
RepoId = "my-org/my-model",
LocalDirectory = "./models/pinned",
RequiredFiles = ["model.onnx"],
Revision = "main",
ExpectedCommitSha = "1234567890abcdef1234567890abcdef12345678"
};
await downloader.DownloadFilesAsync(request);
Console.WriteLine(request.ResolvedCommitSha);
bool ready = downloader.AreFilesAvailable(
["onnx/model.onnx", "tokenizer.json"],
"./models/miniLM");
if (!ready)
{
var missing = downloader.GetMissingFiles(
["onnx/model.onnx", "tokenizer.json"],
"./models/miniLM");
Console.WriteLine($"Missing {missing.Count} files");
}
IReadOnlyList<RepoTreeEntry> entries = await downloader.ListRepoFilesAsync(
"sentence-transformers/all-MiniLM-L6-v2",
RepoType.Model);
foreach (var entry in entries.Where(e => !e.IsDirectory))
Console.WriteLine($"{entry.Path} ({entry.SizeBytes} bytes)");
// Use the discovered paths directly as a download request
await downloader.DownloadFilesAsync(new DownloadRequest
{
RepoId = "sentence-transformers/all-MiniLM-L6-v2",
LocalDirectory = "./models/miniLM",
RequiredFiles = [.. entries.Where(e => !e.IsDirectory).Select(e => e.Path)]
});
var progress = new Progress<DownloadProgress>(p =>
{
if (p.Stage == DownloadStage.Downloading)
Console.Write($"\rβ¬οΈ [{p.CurrentFile}] {p.PercentComplete:F0}% (reused {ByteFormatHelper.FormatBytes(p.ResumedBytes)})");
else
Console.WriteLine($"{p.Stage}: {p.Message}");
});
await downloader.DownloadFilesAsync(new DownloadRequest
{
RepoId = "sentence-transformers/all-MiniLM-L6-v2",
LocalDirectory = "./models/miniLM",
RequiredFiles = ["onnx/model.onnx", "tokenizer.json"],
Progress = progress
});
Set the HF_TOKEN environment variable, or pass it explicitly:
var downloader = new HuggingFaceDownloader(new HuggingFaceDownloaderOptions
{
AuthToken = "hf_your_token_here"
});
Set the HF_ENDPOINT environment variable, or configure the endpoint explicitly:
var downloader = new HuggingFaceDownloader(new HuggingFaceDownloaderOptions
{
Endpoint = "https://huggingface.internal"
});
The explicit Endpoint takes precedence over HF_ENDPOINT; if neither is set, the downloader uses https://huggingface.co.
builder.Services.AddHuggingFaceDownloader(options =>
{
options.Timeout = TimeSpan.FromMinutes(60);
});
// Then inject HuggingFaceDownloader in your services
public class MyModelService(HuggingFaceDownloader downloader)
{
public async Task EnsureModelAsync()
{
await downloader.DownloadFilesAsync(new DownloadRequest
{
RepoId = "my-org/my-model",
LocalDirectory = DefaultPathHelper.GetDefaultCacheDirectory("MyApp"),
RequiredFiles = ["model.onnx", "tokenizer.json"]
});
}
}
var cacheRoot = DefaultPathHelper.GetDefaultCacheDirectory("hfdownload");
var repoId = "sentence-transformers/all-MiniLM-L6-v2";
// List all cached repositories
var cachedRepos = downloader.ListCachedRepos(cacheRoot);
// Check whether required files are cached for one repo
bool isCached = downloader.IsCached(
repoId,
cacheRoot,
["onnx/model.onnx", "tokenizer.json"]);
// Get total cache size for one local repo directory
long size = downloader.GetCachedSize(
Path.Combine(cacheRoot, DefaultPathHelper.SanitizeModelName(repoId)));
// Delete cached files for the repo (safe no-op if not found)
await downloader.DeleteCachedFilesAsync(repoId, cacheRoot);
Direct downloads also write ./models/.../.hf.download.resolved.json so callers can inspect the requested revision and resolved commit later.
| Topic | Description |
|---|---|
| Getting Started | Installation, all usage examples, and setup |
| CLI Reference | Complete CLI command reference |
| API Reference | Complete class and method documentation |
| Architecture | Design decisions, data flow, and project structure |
| Publishing | NuGet publishing with GitHub Actions |
git clone https://github.com/elbruno/ElBruno.HuggingFace.Downloader.git
cd ElBruno.HuggingFace.Downloader
dotnet build ElBruno.HuggingFace.Downloader.slnx
dotnet test ElBruno.HuggingFace.Downloader.slnx
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License β see the LICENSE file for details.
Hi! I'm ElBruno π§‘, a passionate developer and content creator exploring AI, .NET, and modern development practices.
Made with β€οΈ by ElBruno
If you like this project, consider following my work across platforms:
C#
100.0%