CodeOn.NET

Code, Learn, Build in .NET
Theme:
    2025-11-04 · 6 min read

    Running C# without a Project File in .NET 10

    Ever wanted to try out a quick C# idea without setting up a whole project?

    Languages like Python, JavaScript, and Go have had this kind of support for a while - you just write a file and run it. .NET always required a project and a csproj file, but starting with .NET 10, it's now built right into the .NET Core framework.

    What is dotnet run app.cs?


    This new feature in .NET 10 lets you run a single .cs file directly - no project file, no csproj, no boilerplate.

    All the usual MSBuild setup happens automatically behind the scenes: target framework, language version, and performance features like AOT and trimming.

    EnableAotAnalyzer = true
    EnableSingleFileAnalyzer = true
    EnableTrimAnalyzer = true
    TargetFramework = net10.0

    You don't need to configure anything. It's fast, clean, and has better startup time.

    dotnet project convert app.cs - You can convert your single-file app anytime using this command. It moves your code into Program.cs with top-level statements and transfers all #: directives and package dependencies into a traditional .csproj file.

    How it works?


    Create a file called hello.cs with this code:

    Console.WriteLine("Hello, .NET 10!");
    

    Then run it from the terminal

    dotnet run hello.cs
    or
    dotnet hello.cs
    // outputs to 
    Hello, .NET 10!

    Calling an API and printing JSON

    #!/usr/bin/dotnet run
    
    var http = new HttpClient();
    var json = await http.GetStringAsync("https://jsonplaceholder.typicode.com/todos/1");
    Console.WriteLine(json);
    

    The #!/usr/bin/dotnet run line (called a shebang) lets the .cs file run directly on macOS/Linux (e.g., ./script.cs).

    Real-world example - Serving Static HTML locally


    A simple way I use this feature is to run a quick local web server to preview static HTML files. The script serves content from a folder you pass as an argument, or falls back to publish if none is provided.

    #:sdk Microsoft.NET.Sdk
    #:package Microsoft.AspNetCore.App
    
    using Microsoft.AspNetCore.Builder;
    using Microsoft.Extensions.FileProviders;
    
    var publishDirectory = args.Length > 0 ? args[0] : "publish";
    var path = Path.Combine(Directory.GetCurrentDirectory(), publishDirectory);
    
    var builder = WebApplication.CreateBuilder();
    var app = builder.Build();
    
    app.UseDefaultFiles(new DefaultFilesOptions
    {
        FileProvider = new PhysicalFileProvider(path),
        RequestPath = ""
    });
    
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(path),
        RequestPath = ""
    });
    
    app.Run();
    

    The #: lines, like #:sdk, are file-level SDK directives used to define the type of project - for example, a web app. When adding packages, you can use the syntax #:<package>@<version> to specify dependencies directly in the source file. If you omit the version, the latest stable version of the package will be used automatically.

    When to use it?


    This feature is useful in many situations:

    Quick prototyping - Test an idea or a small piece of logic. Just write the code and run it.

    Learning and teaching C# - Great for beginners. Focus on the language without dealing with project structure or build systems.

    Writing automation scripts - Clean up files, call an API, or process some data locally.

    Testing APIs or libraries - Try out a NuGet package or explore an API quickly without creating a new project. You can also reference NuGet packages using the #:package directive. For ex: #:package Markdig@0.42.0

    Limitations and Gotchas


    This feature is super handy, but it's not a one-size-fits-all. A few things to keep in mind:

    Single‑file focus (for now) – Currently you're typically working with one .cs file. Multi‑file workflows (for ex, splitting logic into other .cs files) are not officially supported in this mode yet.

    Tooling & IDE support still evolving – You can debug single‐file apps in vs code using the C# Dev Kit or C# extension. As of now, this isn't supported in the full version of Visual Studio.

    Optimised for scripts and prototypes – This mode is ideal for learning, quick automation, and experimentation. For larger-scale, production‑grade apps you'll likely want a standard project structure and full tooling support.

    var json = JsonSerializer.Serialize(new Person { Name = "Deepu Madhusoodanan" });
    

    It threw an error like:

    Unhandled exception. System.InvalidOperationException: Reflection-based serialization has been disabled for this application. Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property.
       at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_JsonSerializerIsReflectionDisabled()
       at System.Text.Json.JsonSerializerOptions.ConfigureForJsonSerializer()
       at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions options, Type inputType)
       at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options)
       at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)
       at Program.<Main>$(String[] args) in C:\app\json.cs:line 5

    That's because trimming is on by default, and it can remove things that reflection-based APIs like System.Text.Json need. The workaround was to add this at the top of the file:

    #:property PublishTrimmed=false
    #:property WarningLevel=0

    You can also add a project reference by using the #:project property:

    #:project src/myawesomelib.csproj

    This works fine and lets your single‑file app consume code from another project. One thing to be aware of though: if you only make changes in the library project and don’t touch the single file itself, you won’t see those changes reflected when you run. That’s because the single‑file runner caches the build in a temp folder. The cache lives under:

    C:\Users\<username>\AppData\Local\Temp\dotnet\runfile

    To reflect changes from the library, you either need to clear that folder or make a small edit in the single file so the runner rebuilds. Otherwise, the cached version will keep running without your latest library updates.

    Final Thoughts


    I've been using this feature for a few weeks now, and honestly, it's been great. In fact, this very blog was generated using just two standalone C# files - no shared code, no dependencies - everything is independent, so tweaking things is easy without worrying about breaking anything.

    This new way of running C# code is simple, fast, and surprisingly powerful for quick tasks. No setup, no boilerplate - just write and run. If you haven't tried it yet, give it a shot. You might be surprised how useful it becomes.