New in .NET 10.0 [17]: NuGet packages and settings for File-based Apps

Information that is usually in the .csproj project file for normal .NET projects is set in File-based Apps using a preprocessor directive.

listen Print view
C# characters

(Image: Pincasso/Shutterstock)

2 min. read
By
  • Dr. Holger Schwichtenberg

Directly translating and starting C# files Microsoft calls File-based Apps. For information usually found in the .csproj project file, Microsoft has introduced its own syntax for File-based Apps.

The Dotnet Doctor – Holger Schwichtenberg
Der Dotnet-Doktor – Holger Schwichtenberg

Dr. Holger Schwichtenberg is the technical director of the expert network www.IT-Visions.de, which supports numerous medium-sized and large companies with consulting and training services as well as software development, drawing on the expertise of 53 renowned experts. Thanks to his appearances at numerous national and international conferences, as well as more than 90 specialist books and over 1,500 specialist articles, Holger Schwichtenberg is one of the best-known experts for .NET and web technologies in Germany.

The syntax begins with the hash symbol # (a preprocessor directive in C#) followed by a colon (a directive to be ignored from the C# compiler's perspective):

  • Setting the SDK: #:sdk Microsoft.NET.Sdk.Web. When specifying the SDK, from Preview 6 onwards, you can also specify the version number after @, for example #:sdk Aspire.AppHost.Sdk@9.3.1
  • Reference to a NuGet package: #:package Console@0.48.*
  • Reference to projects: #:project ./ClassLib/ClassLib.csproj
  • Build properties, e.g., version number: #:property Version=1.1.2 (before Preview 6, without an equals sign, but with a space as a separator)
  • By default, File-based Apps use the NativeAOT compiler. If you disable it with #:property PublishAot=false, the Just-In-Time compiler is used.

On NuGet.org, each package now has a "File-based Apps" tab with the appropriate syntax for including the package in a standalone C# file (Fig. 1).

Videos by heise

Further features of File-based Apps include:

  • You can create a .settings.json file in the same folder with the settings for the File-based App.
  • You can create a launch profile for the File-based App in the .run.json file in the same folder.
  • You can execute dotnet build Filename.cs or dotnet restore Filename.cs.
  • You can compile such File-based Apps into an executable file (.EXE) with dotnet publish cs.
  • Since Preview 6, within a File-based App, developers can determine the location of the file with AppContext.GetData("EntryPointFileDirectoryPath") and the full path to the executed C# file with GetData("EntryPointFilePath"). However, this only works with File-based Apps, not in normal, project-based C# applications.

However, in .NET 10.0, there is no way yet to directly include another .cs file in a File-based App. This is planned for .NET 11.0.

The following code shows an example of a File-based App with two referenced NuGet packages:

#!/usr/bin/env dotnet
#region Einstellungen fĂĽr File-based App
// https://www.nuget.org/packages/humanizer/
#:package Humanizer@2.14.1 
// https://www.nuget.org/packages/Spectre.Console/
#:package Spectre.Console@0.*
#:property LangVersion=preview
#:property Version=1.2.0
#:project ./ClassLibrary/ClassLibrary.csproj
#endregion
using Spectre.Console;
using Humanizer;

var title = "C# Script v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version +
" mit " + System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription + "\n" +
AppContext.GetData("EntryPointFilePath");

// Header
AnsiConsole.Write(
    new Panel(title)
        .Header("[yellow]File-based App[/]", Justify.Center)
        .Border(BoxBorder.Rounded)
        .BorderStyle(new Style(foreground: Color.Green))
        .Padding(1, 1, 1, 1)
);

// Parameter auflisten
foreach (var arg in args)
{
    Console.WriteLine($"Argument: {arg}");
}

Console.WriteLine();

// Daten
(Data net80, Data net90, Data net10) = GetData();

// Textausgabe in Wochen
var dotNet8Released = DateTimeOffset.Parse(net80.Release);
TimeSpan dotNet8Since = DateTimeOffset.Now - dotNet8Released;
Console.WriteLine($"It has been {dotNet8Since.Humanize()} since .NET {net80.Version} was released.");

var dotNet9Released = DateTimeOffset.Parse(net90.Release);
TimeSpan dotNet9Since = DateTimeOffset.Now - dotNet9Released;
Console.WriteLine($"It has been {dotNet9Since.Humanize()} since .NET {net90.Version} was released.");

var dotNet10Released = DateTimeOffset.Parse(net10.Release);
TimeSpan dotNet10Since = DateTimeOffset.Now - dotNet10Released;
Console.WriteLine($"{dotNet10Since.Humanize()} since .NET {net10.Version} release.");

Console.WriteLine();

// Zeichne Balken für die Anzahl der Tage seit der Veröffentlichung
var bar = new BarChart()
    .Width(100)
    .AddItem("Days since .NET 8.0 release", dotNet8Since.TotalDays, Color.Red)
    .AddItem("Days since .NET 9.0 release", dotNet9Since.TotalDays, Color.Blue)
    .AddItem("Days since .NET 10.0 release", dotNet10Since.TotalDays, Color.Purple);
AnsiConsole.Write(bar);

Console.WriteLine();

// Lokale Funktion
static (Data, Data, Data) GetData()
{
    var net80 = new Data
    {
        Version = "8.0",
        Release = "2023-11-14"
    };
    var net90 = new Data
    {
        Version = "9.0",
        Release = "2024-11-12"
    };
    var net10 = new Data
    {
        Version = "10.0",
        Release = "2025-11-11"
    };
    return (net80, net90, net10);
}

// Datenklasse
class Data
{
    public required string Version { get; set; }
    public string Release { get; set; }
}

The screenshot shows the output when starting the example code (Fig. 2).

(dahe)

Don't miss any news – follow us on Facebook, LinkedIn or Mastodon.

This article was originally published in German. It was translated with technical assistance and editorially reviewed before publication.