.NET 10 Deep Dive: Ad‑hoc tools, the new dnx runner, and File‑based apps superpowers

.NET 10 Deep Dive: Ad‑hoc tools, the new dnx runner, and File‑based apps superpowers

In .NET 10, the CLI picked up a trio of quality‑of‑life upgrades that remove friction from everyday dev and CI workflows:

  • Ad‑hoc tool execution so you can run a .NET tool without installing it.
  • A new dnx runner that makes tool invocation dead simple.
  • Big enhancements to file‑based apps, including publish and native AOT.

In this post, I’ll show why these matter, how they work, and when to reach for each using concise, copy‑pasteable examples you can drop into your Windows PowerShell or CI scripts.

Why you should care

  • Less bootstrapping: Run tools in CI without pre‑install steps or global state.
  • Faster feedback: File‑based apps let you script in C# with instant runs and native AOT publish.
  • Cleaner ergonomics: dnx shortens commands and forwards arguments as‑is to the CLI.

Ad‑hoc tool execution (no install required)

You can now execute a .NET tool straight from NuGet without installing it globally or locally.

  • Command: dotnet tool exec <PackageId>[@version] [args…]
  • Uses the tool version in a nearby .config/dotnet-tools.json if present; otherwise the latest version unless you pin it (for example, toolsay@1.0.0).
  • Prompts before first download (interactive terminals) and caches thereafter.

Example with a local feed source and visible prompt:

PowerShell
# Run the tool once without installing it
dotnet tool exec --source .\artifacts\package\ toolsay "Hello, World!"

Sample output (from docs):

PowerShell
Tool package toolsay@1.0.0 will be downloaded from source <source>.
Proceed? [y/n] (y): y
  _   _          _   _                __        __                 _       _   _
 | | | |   ___  | | | |   ___         \ \      / /   ___    _ __  | |   __| | | |
 | |_| |  / _ \ | | | |  / _ \         \ \ /\ / /   / _ \  | '__| | |  / _` | | |
 |  _  | |  __/ | | | | | (_) |  _      \ V  V /   | (_) | | |    | | | (_| | |_|
 |_| |_|  \___| |_| |_|  \___/  ( )      \_/\_/     \___/  |_|    |_|  \__,_| (_)
                                |/

Tips

  • CI friendly: add --non-interactive to suppress prompts, or pre-restore a manifest (dotnet tool restore).
  • Trust the source: use --source for your internal feed; prefer pinning versions for reproducibility.

The new dnx tool runner

There’s a new dnx CLI command that forwards args to the dotnet CLI for tool execution. It’s the simplest way to “just run a tool”. Think of it as npx, but for .NET tools.

PowerShell
# As simple as it gets
dnx toolsay "Hello, World!"

Notes

  • dnx is implemented by the dotnet CLI; behavior evolves with the SDK.
  • It’s great for quick local runs and scripting; for deterministic CI, prefer dotnet tool exec with explicit sources and versions.

When to use which

  • Use dnx when you want the shortest possible command and you’re okay with the default resolution behavior.
  • Use dotnet tool exec when you need control (version pinning, feed selection, non‑interactive behavior) or you’re running in CI/CD.
  • Use a local tool manifest (dotnet new tool-manifest + dotnet tool install) when your repo standardizes tool versions for all devs.

File‑based apps, level‑up in .NET 10

File‑based apps let you put a complete program in a single *.cs file and run it directly. In .NET 10, they gained publish support and default to native AOT, so your tiny scripts can ship as tiny native executables.

Run a single‑file C# app

C#
// hello.cs
Console.WriteLine("Hello, file-based app!");
PowerShell
# compile+run in one shot
dotnet run .\hello.cs
# Output:
# Hello, file-based app!

Publish (native AOT by default)

PowerShell
# Produce a native executable for Windows x64
dotnet publish .\hello.cs -r win-x64 -c Release -o .\publish
# Result: .\publish\hello.exe (native AOT)

Need to turn AOT off (for example, when using packages or reflection features that aren’t AOT-friendly)? Add a property directive at the top of your file:

C#
#:property PublishAot=false
Console.WriteLine("Running without AOT");

Reference a project from a file‑based app

You can reference projects via the #:project directive—no .csproj required for the app.

C#
// app.cs
#:project ..\ClassLib\ClassLib.csproj

var greeter = new ClassLib.Greeter();
Console.WriteLine(greeter.Greet(args.Length > 0 ? args[0] : "World"));
PowerShell
dotnet run .\app.cs Roxeem
# Output:
# Hello, Roxeem!

Shebang (Unix/macOS)

On Unix shells, you can run file‑based apps directly with a shebang. Example shown for completeness if you’re cross‑platform:

Bash
#!/usr/bin/env dotnet
Console.WriteLine("Hello shebang!");

Then mark executable and run (chmod +x app), no dotnet run needed. On Windows, prefer dotnet run <file>.cs.

Bonus for tool authors: platform‑specific tools and the any RID

If you author .NET tools, .NET 10 lets you ship multiple RIDs in one package, and even include any for broad compatibility. Example csproj snippet:

XML
<PropertyGroup>
  <PackAsTool>true</PackAsTool>
  <RuntimeIdentifiers>
    linux-x64;linux-arm64;macos-arm64;win-x64;win-arm64;any
  </RuntimeIdentifiers>
</PropertyGroup>

You can also use the full suite of publish options (self‑contained, trimmed, AOT) for tools, not just apps.

Practical CI examples

Ad‑hoc formatting in a pipeline:

PowerShell
dotnet tool exec dotnet-format --verify-no-changes

Deterministic restore of repo‑pinned tools:

PowerShell
dotnet tool restore
dotnet dotnetsay --version

Quick local run with dnx:

PowerShell
dnx dotnetsay "CI passed!"

Cheat sheet

  • Ad‑hoc tools: dotnet tool exec <pkg>[@ver] [args] (use --non-interactive in CI; --source for feeds)
  • dnx runner: dnx <tool> [args] (super short; great for local use)
  • File‑based apps: dotnet run <file>.cs and dotnet publish <file>.cs -r <rid>
  • Disable AOT for file‑based apps: #:property PublishAot=false
  • Reference a project from a file: #:project <path-to-.csproj>

References

Discover more from Roxeem

Subscribe now to keep reading and get access to the full archive.

Continue reading