Project Structure & Program.cs
When you create a project from a template, a dozen files and folders appear. Some you will edit every day, some you will rarely open, and a few you should never commit to Git. Knowing what each one does makes the rest of the framework far less mysterious.
In this lesson you will walk through a Razor Pages project created with dotnet new webapp, read its Program.cs line by line, and learn the two phases every ASP.NET Core app goes through: registering services, then building the request pipeline.
The files in a new project
Create a project for a clinic in Pune:
dotnet new webapp -o ShantiClinicThe template produces this layout (the bin and obj folders appear after the first build):
ShantiClinic/
ShantiClinic.csproj project file: SDK, target framework, packages
Program.cs start-up code: services and pipeline
appsettings.json settings for every environment
appsettings.Development.json extra settings used only on your machine
Properties/
launchSettings.json how `dotnet run` starts the app locally
Pages/ Razor Pages: .cshtml views + .cshtml.cs code
Index.cshtml
Index.cshtml.cs
Privacy.cshtml
Error.cshtml
Shared/_Layout.cshtml the common page layout
_ViewImports.cshtml shared @using lines and tag helpers
_ViewStart.cshtml chooses the layout for every page
wwwroot/ public static files: css, js, lib, favicon
bin/, obj/ build output (never commit these)A webapi project is smaller: no Pages or wwwroot folder, but an extra ShantiClinic.http file with sample requests. An mvc project has Controllers, Models and Views folders instead of Pages.
The project file
Open ShantiClinic.csproj. It is short because the Web SDK supplies sensible defaults:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>Sdk="Microsoft.NET.Sdk.Web"tells the build that this is a web app. It references the ASP.NET Core shared framework and treatswwwroot,.cshtmland.jsonfiles correctly.net10.0is the target framework: the version of .NET the app runs on.Nullableswitches on nullable reference type warnings, andImplicitUsingsadds commonusinglines (such asMicrosoft.AspNetCore.BuilderandMicrosoft.Extensions.DependencyInjection) to every file, which is whyProgram.csneeds nousingstatements.
Program.cs: two phases
Program.cs uses C# top-level statements: there is no class Program or Main method to write, the file itself is the entry point. Here is the Razor Pages start-up code, with comments added to show the two phases:
// Phase 1: create the builder and register services
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
// Build the app. After this line, the list of services is locked.
var app = builder.Build();
// Phase 2: configure the request pipeline (middleware), in order
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapStaticAssets();
app.MapRazorPages()
.WithStaticAssets();
// Start the server and block until it shuts down
app.Run();Phase 1 works with builder. WebApplication.CreateBuilder(args) prepares the defaults: configuration from appsettings.json, environment variables and command-line arguments; logging to the console; the Kestrel server; and an empty service collection. You add the features your app needs to builder.Services. Here AddRazorPages() registers everything Razor Pages needs.
Phase 2 works with app. Each Use... call adds a piece of middleware to the pipeline that every request passes through, in the order written. Each Map... call adds endpoints: MapStaticAssets() serves files from wwwroot, and MapRazorPages() creates an endpoint for every page in the Pages folder.
| Line | What it does |
|---|---|
UseExceptionHandler("/Error") | Outside Development, shows the friendly /Error page when code throws |
UseHsts() | Tells browsers to use only HTTPS for this site from now on |
UseHttpsRedirection() | Redirects http:// requests to https:// |
UseRouting() | Matches the request to an endpoint |
UseAuthorization() | Checks whether the user may reach that endpoint |
app.Run() | Starts Kestrel; code after it runs only after shutdown |
The order is not decoration: moving lines changes behaviour. You will study it closely in The Middleware Pipeline.
Settings files
appsettings.json holds settings for every environment. The template's version controls logging and allowed host names:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}appsettings.Development.json is loaded on top of it when the app runs in the Development environment, so values there win on your machine. You will add your own sections, such as clinic opening hours and connection strings, in Configuration & Environments.
Properties/launchSettings.json is used only by dotnet run, Visual Studio and VS Code on your machine. It is never used on a server. A trimmed version looks like this:
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:7080;http://localhost:5080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}Each profile sets the URLs and the environment. dotnet run uses the first profile; dotnet run --launch-profile https picks another.
wwwroot and the Pages folder
wwwroot is the web root: files inside it can be downloaded by anyone who knows the URL. wwwroot/css/site.css is served at /css/site.css. Never put secrets or private files here.
Pages holds Razor Pages. The file path becomes the URL: Pages/Index.cshtml answers /, and Pages/Privacy.cshtml answers /Privacy. Each .cshtml file (HTML with C#) can have a .cshtml.cs page model class next to it that holds the C# logic. Files that start with an underscore, such as _Layout.cshtml, are not pages; they are shared pieces used by pages.
The folder that contains the project file is the content root. ASP.NET Core looks for appsettings.json, Pages and wwwroot relative to it, which is why you should start the app from the project folder or use dotnet run --project.
Older code: Startup.cs
In tests you sometimes need to refer to the Program class. Top-level statements generate an internal class, so older projects added public partial class Program { } at the bottom of Program.cs. In .NET 10 a source generator makes Program public automatically, so that line is no longer needed.
Common mistakes
- Registering services after
Build(). Everything onbuilder.Servicesmust come first. - Expecting
launchSettings.jsonon the server. It only affects local runs. Production URLs and environment come from environment variables or configuration. - Putting private files in
wwwroot. Everything there is public. - Running from the wrong folder. If
appsettings.jsonvalues seem missing, check the content root path printed at start-up.
Key points
- The
.csprojusesMicrosoft.NET.Sdk.Weband targetsnet10.0; nullable and implicit usings are on. Program.cshas two phases: register services onbuilder.Services, then build the pipeline onapp.Use...calls add middleware in order;Map...calls add endpoints;app.Run()starts the server.appsettings.jsonandappsettings.{Environment}.jsonhold settings;launchSettings.jsonis for local runs only.wwwrootis public,Pagesmaps file paths to URLs, and the oldStartup.cspattern maps directly onto today'sProgram.cs.
Exercise
In your ShantiClinic project, add a page called Timings by creating Pages/Timings.cshtml with a @page line and a heading "Clinic Timings: 10:00 to 13:00". Run the app and open /Timings. Then answer: which line in Program.cs made the new page reachable without any extra code, and what URL would a page at Pages/Doctors/List.cshtml have?
Show solution
@page
<h1>Clinic Timings: 10:00 to 13:00</h1>app.MapRazorPages() creates an endpoint for every .cshtml file under Pages that starts with @page, so the new file is reachable at /Timings with no other changes. A page at Pages/Doctors/List.cshtml would answer /Doctors/List. The page also appears inside the shared layout, because _ViewStart.cshtml applies _Layout.cshtml to every page.
Quick Quiz
1. What is the difference between the code above and below builder.Build() in Program.cs?
Services are registered on builder.Services before Build(); the pipeline is configured on app after it.
2. Where is launchSettings.json used?
Launch settings are a development convenience and are ignored in deployed apps.
3. Which file would be served to a browser that requests /css/site.css?
wwwroot is the web root; its files are served from the site root path.
4. In older projects, which method of the Startup class corresponds to registering services?
ConfigureServices registered services; Configure built the pipeline.
Learn ASP.NET Core with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
