Microsoft's Azure Copilot Migration Agent can turn complex migration data into clear answers. With natural language prompts, you can evaluate readiness, risk, ROI, and automate landing zone requirements to make confident decisions for you and your team. Streamline planning and analysis, so your migrations are better scoped, better justified, and far less error-prone.
What Azure Copilot Agents Can Actually Do β Learning Module (Sponsored)
Microsoft has made an Introduction to Azure Copilot Agents learning module available.
The six specialized agents are embedded throughout the lifecycle to automate and accelerate specific tasks, cutting down on cloud operations tasks like deployments, monitoring, optimization, and troubleshooting.
Deployment agent: Helps automate infrastructure setup and application deployments by generating configurations and guiding execution within Azure environments.
Observability agent: Monitors system health by analyzing metrics, logs, and signals to provide actionable insights and improve visibility.
Optimization agent: Identifies opportunities to reduce costs and improve performance across resources using intelligent recommendations.
Resiliency agent: Strengthens system reliability by suggesting improvements for fault tolerance, availability, and recovery strategies.
Troubleshooting agent: Assists in diagnosing issues by correlating signals across services and guiding root cause analysis.
Migration agent: Simplifies moving workloads to Azure by assessing environments, recommending strategies, and orchestrating migration steps.
The learning module is a great way to get started with all six agents.
For years, even the smallest C# program required a solution file, a project file, and a folder structure.
While Python and JavaScript developers could create a single file and run it in seconds, .NET developers had to create a solution and add a csproj file just to test an idea.
That changed with .NET 10, which introduced file-based apps.
Now you can run a C# file directly with dotnet run.
And in April 2026 in .NET 11 Preview 3 we received a support for using multiple files in file-based apps with the #:include directive.
This finally makes C# a real option for scripts, automation, internal tooling, and quick prototypes - without the overhead of a full project.
Traditionally, every C# application required three things:
Solution file (*.sln)
Project file (*.csproj)
Source code (*.cs).
Even for a 10-line script, you had to create a new solution, add a project with dotnet new console, wait for the scaffolding to finish, and only then write your actual code.
Starting with .NET 10, you can skip all of that.
You can create a single .cs file and run it directly:
bash
1dotnet run main.cs
That's it. No project file. No solution file. Just one file.
This puts C# on equal footing with Python, JavaScript, Node.js, and other scripting languages.
For CLI utilities, automation tasks, and one-off tools, this completely changes the workflow.
Let's see the simplest possible example. Create a file called hello.cs:
csharp
1Console.WriteLine("Hello from a file-based app!");2Console.WriteLine($"Today is {DateTime.Now:dddd, MMMM d, yyyy}");
Then run it:
bash
1dotnet run hello.cs
Notice there is no Main method, no class Program, no using statement.
File-based apps build on top of top-level statements (introduced in C# 9) and implicit usings, so you can write code as if you were in a script.
Behind the scenes, the .NET CLI compiles your file in a temporary location and runs the resulting binary.
The first run takes a moment, but subsequent runs are cached and fast.
I've used this many times in the last year for small tasks I would have otherwise written in Python (or created a solution project):
A real script usually needs more than the BCL.
You probably want JSON parsing, HTTP calls, or database access.
File-based apps support special # directives at the top of your file to declare dependencies.
The #:package Directive
Use #:package to reference any NuGet package:
csharp
1#:package [email protected]23usingNewtonsoft.Json;45var data =new{ Name ="Anton", Year =2026};6var json = JsonConvert.SerializeObject(data, Formatting.Indented);7Console.WriteLine(json);
The format is #:package PackageName@Version.
The .NET CLI restores the package on first run, just like it would for a regular project.
The #:sdk Directive
Use #:sdk when you need a specific SDK, such as the Web SDK for ASP.NET Core:
csharp
1#:sdk Microsoft.NET.Sdk.Web23var builder = WebApplication.CreateBuilder();4var app = builder.Build();56app.MapGet("/",()=>"Hello from a single-file API!");78app.Run();
By default, file-based apps use Microsoft.NET.Sdk (the standard SDK).
Switching to Microsoft.NET.Sdk.Web unlocks ASP.NET Core types like WebApplication without any project setup.
The #:project Directive
If you need to call into an existing project (for example, a shared class library), use #:project:
In .NET 10, file-based apps had one significant limitation: everything had to fit in a single file.
That worked for short scripts, but the file grew too big for anything larger.
.NET 11 Preview 3 fixes this with the #:include directive.
You can now split your code across multiple files while keeping the file-based workflow.
Imagine you are writing a small data processor. In .NET 10, you had to put models, helpers, and the main logic all in one file.
Now you can split it cleanly:
The #:include directive tells the compiler to include the referenced file in the compilation.
You can include as many files as you need, and they can live in subfolders:
Roslyn also adds editor IntelliSense for the directive, so completion works when you type #:include in your main file.
This completes the file-based apps feature.
You can start with a single file, grow it into multiple files as your script matures, and convert it to a full project only when you truly need one.
The HealthChecker class takes an HttpClient and exposes a single CheckAsync method.
It captures the response time, status code, and any exception that occurs.
The Entry Point
Create main.cs with the URLs to monitor and the polling loop:
csharp
1#:include healthchecker.cs23var urls =new[]4{5"https://github.com",6"https://example.com"7};89usingvar http =newHttpClient{ Timeout = TimeSpan.FromSeconds(5)};10var checker =newHealthChecker(http);1112Console.WriteLine($"Monitoring {urls.Length} URLs every 5 seconds. Press Ctrl+C to stop.");13Console.WriteLine();1415while(true)16{17foreach(var url in urls)18{19var result =await checker.CheckAsync(url);20var status = result.IsHealthy ?"OK ":"FAIL";21var error = result.Error isnull?"":$" - {result.Error}";2223 Console.WriteLine(24$"[{DateTime.Now:HH:mm:ss}] {status}{result.StatusCode} "+25$"{result.Url} ({result.ResponseTimeMs}ms){error}");26}2728 Console.WriteLine();29await Task.Delay(TimeSpan.FromSeconds(5));30}
The main file pulls in healthchecker.cs with #:include and runs an infinite loop that checks each URL every 5 seconds.
To run the app, execute the following command at the main file:
bash
1dotnet run main.cs
You'll see output like this:
1Monitoring 3 URLs every 5 seconds. Press Ctrl+C to stop.
23[14:32:01] OK 200 https://github.com (412ms)
4[14:32:02] OK 200 https://example.com (89ms)
56[14:32:07] OK 200 https://github.com (398ms)
7[14:32:07] OK 200 https://example.com (76ms)
The mappings live directly inside OnModelCreating for simplicity.
For larger projects, you would extract them into separate IEntityTypeConfiguration<T> classes - but for a file-based app, inline mappings are perfect.
Now the entry point.
Create main.cs with the SDK directive, package directive, includes, DI setup, and the API endpoints:
csharp
1#:sdk Microsoft.NET.Sdk.Web2#:package [email protected]3#:include Order.cs4#:include OrdersDbContext.cs56usingMicrosoft.EntityFrameworkCore;78var builder = WebApplication.CreateBuilder();910builder.Services.AddDbContext<OrdersDbContext>(options =>11 options.UseSqlite("Data Source=orders.db"));1213var app = builder.Build();1415// Make sure the database and table exist before handling requests16using(var scope = app.Services.CreateScope())17{18var db = scope.ServiceProvider.GetRequiredService<OrdersDbContext>();19await db.Database.MigrateAsync();20}2122app.MapGet("/orders",async(OrdersDbContext db)=>23await db.Orders.AsNoTracking().ToListAsync());2425app.MapGet("/orders/{id:int}",async(int id,OrdersDbContext db)=>26await db.Orders.FindAsync(id)is{}order
27? Results.Ok(order)28: Results.NotFound());2930app.MapPost("/orders",async(Order order,OrdersDbContext db)=>31{32 db.Orders.Add(order);33await db.SaveChangesAsync();34return Results.Created($"/orders/{order.Id}", order);35});3637app.MapDelete("/orders/{id:int}",async(int id,OrdersDbContext db)=>38{39var order =await db.Orders.FindAsync(id);40if(order isnull)return Results.NotFound();4142 db.Orders.Remove(order);43await db.SaveChangesAsync();44return Results.NoContent();45});4647app.Run();
Look at the top of the file. There's a lot going on in just four lines:
#:sdk Microsoft.NET.Sdk.Web switches to the web SDK so we can use WebApplication
There comes a point when a file-based app is no longer enough.
Maybe you need multiple projects, complex build steps, or proper packaging for distribution.
You can use the .NET CLI to convert your file-based app into a regular project with a single command:
bash
1dotnet project convert main.cs
What You Get
The command creates a new folder next to your file (named after your main file) containing a regular project structure:
The csproj file includes the SDK and package references declared with #: directives in your script.
The #:include directives are removed because all files are now part of the project naturally.
Your main.cs becomes a standard top-level statements file.
You can then open the folder in your IDE and continue working as if you had created the project from scratch.
When to Convert
File-based apps are great until they're not. Convert to a full project when:
You need multiple projects in one solution - for example, splitting a Web API and a class library
You need MSBuild customizations - custom targets, conditional compilation, build properties
You need proper packaging - publishing as a NuGet package or a self-contained executable
You need CI/CD with custom steps - pipelines tend to assume a project file exists
The team grows - working with csproj is more familiar to most .NET developers
You need test projects - unit tests work best in a multi-project solution structure
The good news is that you don't have to decide upfront.
Start as a file-based app, grow it as needed, and convert only when you exceed the file-based approach.
File-based apps are the most useful productivity feature .NET has shipped in years.
What started in .NET 10 as a single-file workflow becomes a real, scalable approach in .NET 11 Preview 3 with multi-file support.
When to use file-based apps:
Quick scripts, prototypes, and proof-of-concept work
CLI tools and automation utilities
Internal tooling that doesn't need a full project structure
Demos and learning material where setup gets in the way
Glue code that calls existing libraries or APIs
When to use full projects:
Production applications that need long-term maintenance
Solutions with multiple projects (libraries, APIs, tests)
Anything that requires custom MSBuild logic or packaging
Code that needs to be distributed as a NuGet package
I've already replaced several Python utility scripts with file-based C# apps in my own toolbox.
The strong typing, IntelliSense, and ability to share code with my main projects make them genuinely better tools.
Are you using file-based apps yet?
Stay subscribed to my newsletter for more practical .NET content like this.
Hope you find this newsletter useful. See you next time.
Whenever you're ready, here's how I can help you:
The .NET Senior Playbook is built to:
Fast-track you from junior or mid-level to senior
Keep you growing as a senior
Help you beat any .NET interview
Covers everything: C#, ASP.NET Core, EF Core, system design β answer each question first, reveal the solution, and a test after every chapter proves it stuck. Finish, and you earn a verifiable certificate for your LinkedIn.