Exploring the features of dotnet run app.cs: Exploring the .NET 10 preview - Part 1 : Andrew Lock

Exploring the features of dotnet run app.cs: Exploring the .NET 10 preview - Part 1
by: Andrew Lock
blow post content copied from  Andrew Lock | .NET Escapades
click here to view original post


In this post I describe the new feature coming in .NET 10 for building and running a single C# file without needing to first create a .csproj project. I show it in action, describe the features, and discuss the limitations. In the next post I look at how it works behind the scene.

I struggled a bit with what to call this post, because there doesn't seem to be a final name for the feature I'm describing 😅 In some places it's called "file-based programs", internally it's sometimes referred to as "runfile", but publicly it's described more as "dotnet run app.cs", which describes how you use it.

What is dotnet run app.cs?

Prior to .NET 10, a "hello world" C# application required at least 2 files:

Now .NET 10 includes preview support for removing the need for the .csproj file too. Finally, a .NET application only needs a single .cs file. The hello-world C# program now becomes:

Console.WriteLine("Hello, world");

Save that to a file, app.cs for example, and you can run your application with dotnet run app.cs:

> dotnet run app.cs
Hello, world

This was first demoed by Damian Edwards in this short 15 minute session at MSBuild where he gives a good overview. You can also read more about it in the announcement post. In the next section I'll describe the basic features that are available with the new single-file experience.

What features are available?

The single-file dotnet run experience is currently relatively limited and yet also provides enough configuration points for you to get a long way before you hit a wall. The following small example shows a simple no-op Aspire app host implemented as a single file. It doesn't actually do anything, it's just for demonstrating all the features available in .NET 10 preview 5. I'll walk through this same, and the features it uses, from top to bottom:

#!/usr/bin/dotnet run

#:sdk Microsoft.NET.Sdk
#:sdk Aspire.AppHost.Sdk 9.3.0

#:package [email protected]

#:property UserSecretsId 2eec9746-c21a-4933-90af-c22431f35459

using Microsoft.Extensions.Configuration;

var builder = DistributedApplication.CreateBuilder(args);
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
    { "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL", "https://localhost:21049" },
    { "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL", "https://localhost:22001" },
    { "ASPNETCORE_URLS", "https://localhost:17246" },
});

builder.Build().Run();

This script demonstrates all of the new directives available with the single run file experience.

Making a file executable with a shebang

The line starting with #! at the top of the script is called a shebang, and is a directive for *nix systems that allow you to run the file directly. In this case, it tells the system to run the file using /usr/bin/dotnet run <file>. If you make the file executable using chmod, then you can run it directly from your shell:

chmod +x app.cs
./app.cs

Adding SDK references

By default, single-file apps will use the Microsoft.NET.Sdk MSBuild project SDK by default, which is what you need for building console apps. However, if you're building ASP.NET Core apps, for example, then you need the Microsoft.NET.Sdk.Web SDK, and you need to set that using the #:sdk syntax.

MSBuild project SDKs are what you normally see referenced in the <Project> element in your .csproj file. You don't normally have to worry about them much, because dotnet new templates create the correct values for you.

In the Aspire example above you'll actually see two #:sdk references:

#:sdk Microsoft.NET.Sdk
#:sdk Aspire.AppHost.Sdk 9.3.0

The first #:sdk sets the project SDK, and then the second #:sdk specifies the Aspire additive MSBuild project SDK, which is a versioned SDK that comes from a NuGet package.

The current syntax for specifying the version of versioned SDKs may well change. As of .NET preview 5, the syntax uses a space separator, but is changing to use @ in the future e.g. #:sdk [email protected].

The Aspire.AppHost.Sdk SDK comes from a NuGet package, so obviously there's a way to specify NuGet package references too!

Adding NuGet package references

You can reference NuGet packages using the #:package directive, providing a package name and a version. The following installs version 9.3.0 of the Aspire.Hosting.AppHost NuGet package:

#:package [email protected]

You can also use wildcards for version numbers, so all of the following are also valid:

#:package Aspire.Hosting.AppHost@*
#:package Aspire.Hosting.AppHost@9.*
#:package [email protected].*

The wildcard will generally resolve to the highest package version that satisfies the other requirements.

Updating MSBuild properties

The final (currently) supported directive is #:property which is used to define MSBuild properties for the app. Basically any properties that you would normally define in a .csproj file in a <PropertyGroup> can be added using this directive. In my example, I set the UserSecretsId property value using:

#:property UserSecretsId 2eec9746-c21a-4933-90af-c22431f35459

This is another example where the syntax will be changing shortly. Instead of space-separated values, you will need to use = separated values, e.g.

#:property UserSecretsId=2eec9746-c21a-4933-90af-c22431f35459

That's all the directives that are supported as of .NET 10 preview 5, but in .NET preview 6 you'll have one more available.

Referencing projects (coming soon)

.NET 10 preview 6 should include the ability for single files to reference projects via a #:project directive. This work is already merged, but it's not released yet, so it could change before then. Currently, you can either reference a project using the full path to the .csproj file, or simply reference the directory containing the project:

#:project ../src/MyProject
#:project ../src/MyProject/MyProject.csproj

Being able to reference the project directory instead of the full .csproj path is a nice way to reduce a bit of duplication. And it's pretty much in line with the philosophy Damian Edwards and others described on a recent community standup: given this whole experience is new, they want to make it as smooth as possible for newcomers coming in, which means smoothing off any rough edges they can.

Who is dotnet run app.cs for?

That brings us nicely to the question: who is the single-file experience actually for? First and foremost, the .NET team have made it clear that this is about making the learning experience for newcomers to .NET as smooth as possible. Many other languages, whether they're Node.js or Python, for example, have a single-file experience, and now .NET does too.

Overall, the feature is intended to shield you from all the things you don't care about when you're very first learning C# and .NET; i.e. the project file. Think of yourself as a brand new C# user. You run dotnet new console to create a new console app. The Program.cs file is simple and elegant, just a Console.WriteLine() but you also have this mysterious .csproj file, filled with XML of all things, and with a bunch of stuff that will likely nothing to you 😅

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

With single-file run, that all goes away. As a newcomer you can start from scratch, just the .cs file, and can slowly introduce concepts as you go.

Eventually you'll hit a point where it does make sense to have a dedicated project—maybe you want to have multiple .cs files, for example. At that point, you can simply convert your single-file to a project, by running:

dotnet project convert app.cs

Running that command on the Aspire app host example I showed at the start of this post creates a project file that looks like this:

<Project Sdk="Microsoft.NET.Sdk">

  <Sdk Name="Aspire.AppHost.Sdk" Version="9.3.0" />

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <PropertyGroup>
    <UserSecretsId>2eec9746-c21a-4933-90af-c22431f35459</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Aspire.Hosting.AppHost" Version="9.3." />
  </ItemGroup>

</Project>

All the directives I added are included in the project, and you can see the other defaults that I didn't specify there too now. This is a very slick grow-up story for moving from single-file projects to a project file.

Although newcomers are the main target audience for the single-file experience, there's several scenarios where removing the heaviness of needing a dedicated project (and corresponding directory) makes sense. For example:

  • Utility scripts. Previously you would likely use bash or PowerShell, but now you can easily use C# if you prefer.
  • Samples. Many libraries or frameworks have multiple sample apps for demonstrating a feature, each of which would need its own folder and project file. Now you can have a single folder where each .cs file is a sample app.

That covers a couple of the main uses, but we can also consider uses for which single-file programs are already used! This is not the first attempt at creating a single-file or scripting experience for .NET. For example, I first used a single-file C# experience with Cake, but there are various other approaches too, such as the dotnet-script tool, CS-Script, or even the C# REPL CLI tool, CSI.exe. Each of those tools work in slightly different ways, many of which may well also lend themselves to the new built-in experience.

Going further with escape hatches

I've covered a lot of the features that are explicitly part of the dotnet run app.cs experience, but the fact that the whole feature is built around a "virtual" project, means that you can somewhat hack around current limitations in the directives. Put another, way, there are various files that the single-file app will implicitly use if they're available. These include:

I won't go into details about all these files here, so see the linked documentation if you've not heard about them (the .rsp files were new to me)! Arguably the most useful of these files is the Directory.Build.props which essentially allows you to "enhance" your single-file app with anything that you would normally put in a .csproj file. This is particularly useful if you have, for example, a bunch of single-file apps in a directory, and you want to set a property or add a package to them all, without updating each one.

That's all a bit abstract, but you can see various examples of this kind of thing in Damian Edwards' playground repository for the feature!

Before we finish, I'll describe some of the features that should be coming soon to the dotnet run app.cs experience .

What else is coming?

In this section I'll highlight a few things that are very likely to be part of the future single-file experience. As always with preview features, there's no guarantee that they'll make the final cut, but the bets are good for the first few features at least given they're already merged!

Publishing single file apps

One feature which isn't available in .NET 10 preview 5, but which is already merged for preview 6, is the ability to publish your single-file apps using:

dotnet publish app.cs

What's more, by default, the apps are published as NativeAOT apps! You can disable that by adding #:property PublishAot false to your project, but it's likely that things will just work for many of the scenarios single-file apps are designed for🤷‍♂️

Running with dotnet app.cs

Another feature that's already merged is support for running single-file apps without using the run command, i.e. you can use

dotnet app.cs

instead of having to use

dotnet run app.cs

One of the main advantages to this is that it makes the support for shebangs on linux more robust. For example, if you want to use /usr/bin/env in order to "find" the dotnet executable, instead of assuming it's in /usr/bin/dotnet then previously you would need something like this:

#!/usr/bin/env dotnet run
// ^ Might not work in all shells. "dotnet run" might be passed as a single argument to "env".

Unfortunately, this multi-argument requirement might not work in some shells. With the new dotnet app.cs support however, you can use the simpler, and more widely supported:

#!/usr/bin/env dotnet
// ^ Should work in all shells.

Running C# directly from stdin code

Support for piping C# code directly to dotnet run was merged recently and means you can do things like this:

> 'Console.WriteLine("Hello, World!");' | dotnet run -
Hello, World!

This pipes a hello world app directly from the console to dotnet run and executes it. The classic case of "you should never do this but people do all the time" of curl-ing a website and running it directly is now possible 😅

> curl -S http://totally-safe-not-scary-at-all.com/ | dotnet run -
All your bases are belong to us!

Future ideas

You can see all the other issues that have been opened (or closed) for the feature by looking for the Area-run-file label on GitHub. You can also read more about the feature in general, including much of what's in this post, as well as other planned work here.

What is not coming?

So that covers the features that are currently shipped, as well as a bunch of features that will be shipping very soon, and some potential features that may ship with .NET 10. But what about the other side; what's not coming?

One big feature that is not coming to .NET 10 is support for multiple files. This was originally planned to be included, with things like "nested" files and sub directories being implicitly included in the compilation. Instead, this work has been pushed back to .NET 11, to focus on making the single-file experience as good as possible.

You can indirectly get support for multiple files using Directory.Build.props and Directory.Build.targets and adding references to the files "manually".

Another thing that was mentioned in a recent community standup is that single-file support is not coming to Visual Studio. The 1st party support from Microsoft will only be in Visual Studio Code (and the CLI, obviusly). There's already an issue for adding Rider support here so be sure to upvote it if you're interested in support!

Finally, it's worth saying that at this stage, single-file support is only coming for .cs files; not for .vb or .fs files. The team haven't entirely ruled this out as a possibility, but it's very unlikely Microsoft will add support themselves.

Summary

In this post I described the new single-file experience of dotnet run app.cs coming to .NET 10. I showed how the basic feature works and described the various features supported today: #:package, #:property, #:sdk, and shebangs. I then described the target audience for the feature, described some of the other files you can reference, and discussed some new features that are coming soon. Finally, I described some of the scenarios and environments that will not be supported. In the next post I'll dig into some of the SDK code itself and show how some of the features are implemented.


July 01, 2025 at 03:30PM
Click here for more details...

=============================
The original post is available in Andrew Lock | .NET Escapades by Andrew Lock
this post has been published as it is through automation. Automation script brings all the top bloggers post under a single umbrella.
The purpose of this blog, Follow the top Salesforce bloggers and collect all blogs in a single place through automation.
============================

Salesforce