EthicsComputerCourses
Join training +91 90532 10052

Project Structure & Program.cs

ASP.NET Core · Lesson 4 of 29 Intermediate 8 min read Updated

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:

Terminal
dotnet new webapp -o ShantiClinic

The template produces this layout (the bin and obj folders appear after the first build):

Text
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:

XML
<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 treats wwwroot, .cshtml and .json files correctly.
  • net10.0 is the target framework: the version of .NET the app runs on.
  • Nullable switches on nullable reference type warnings, and ImplicitUsings adds common using lines (such as Microsoft.AspNetCore.Builder and Microsoft.Extensions.DependencyInjection) to every file, which is why Program.cs needs no using statements.

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:

C#
// 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.

LineWhat 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:

JSON
{
  "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:

JSON
{
  "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 on builder.Services must come first.
  • Expecting launchSettings.json on 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.json values seem missing, check the content root path printed at start-up.

Key points

  • The .csproj uses Microsoft.NET.Sdk.Web and targets net10.0; nullable and implicit usings are on.
  • Program.cs has two phases: register services on builder.Services, then build the pipeline on app.
  • Use... calls add middleware in order; Map... calls add endpoints; app.Run() starts the server.
  • appsettings.json and appsettings.{Environment}.json hold settings; launchSettings.json is for local runs only.
  • wwwroot is public, Pages maps file paths to URLs, and the old Startup.cs pattern maps directly onto today's Program.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
Razor
@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?

2. Where is launchSettings.json used?

3. Which file would be served to a browser that requests /css/site.css?

4. In older projects, which method of the Startup class corresponds to registering services?

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