ASP.NET Core MVC · Foundations

Program.cs

Program.cs is where your app is assembled. It has a clear shape — register services, build, configure the pipeline, map endpoints, run — and once you see that shape every ASP.NET Core project becomes easy to read.

Beginner 5 min Lesson 4 of 42Examples target .NET 9.
Before you start:Project Creation
0/42 in ASP.NET Core MVC
On this page · 5 min · Beginner

In simple words#

Program.cs is the app’s ignition and dashboard: register what the app needs (services), then set up the route every request travels (middleware), then start the engine.

builder.Services
Register services for DI.
app.Use…
Add middleware in order.
app.Run()
Start listening for requests.

Learning objectives#

  • Read the four phases of Program.cs
  • Register services on builder.Services
  • Configure the pipeline differently per environment
  • Map MVC routes and static assets

Explanation#

WebApplication.CreateBuilder(args) creates a builder with sensible defaults: Kestrel, configuration from appsettings.json, environment variables and command-line args, logging to the console, and a dependency injection container.

Phase 1 — services: builder.Services.AddControllersWithViews(), AddDbContext, AddScoped<IMyService, MyService>()… Everything your app needs is registered here, before Build().

Phase 2 — build: var app = builder.Build(); After this, the service list is locked.

Phase 3 — pipeline: app.UseExceptionHandler, UseHsts, UseHttpsRedirection, UseRouting, UseAuthentication, UseAuthorization… Order matters; production-only middleware sits inside if (!app.Environment.IsDevelopment()).

Phase 4 — endpoints and run: MapStaticAssets() serves wwwroot with fingerprinting and caching (.NET 9), MapControllerRoute defines the conventional route, and app.Run() starts listening until the app is stopped.

Syntax#

C# Illustrative
var builder = WebApplication.CreateBuilder(args);   // defaults: Kestrel, config, logging, DI
builder.Services.Add…();                             // 1. services
var app = builder.Build();                           // 2. build
app.Use…();                                          // 3. middleware pipeline
app.Map…();                                          // 4. endpoints
app.Run();

Code example: ShopSmart’s Program.cs#

C#Program.cs Illustrative
// Program.cs
var builder = WebApplication.CreateBuilder(args);


// ── 1. Register services ──
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<IProductCatalog, InMemoryProductCatalog>();


// ── 2. Build the app ──
var app = builder.Build();


// ── 3. Configure the HTTP request pipeline ──
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");   // friendly error page in production
    app.UseHsts();                            // tell browsers to always use HTTPS
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();


// ── 4. Map endpoints and run ──
app.MapStaticAssets();                        // wwwroot files, fingerprinted and cached
app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}")
    .WithStaticAssets();


app.Run();


// Services/IProductCatalog.cs
public interface IProductCatalog
{
    IReadOnlyList<string> GetNames();
}


public class InMemoryProductCatalog : IProductCatalog
{
    public IReadOnlyList<string> GetNames() => ["Wireless Mouse", "USB-C Hub", "Mechanical Keyboard"];
}
Expected output
The app starts, prints “Now listening on: https://localhost:7046”, and serves ShopSmart. In Development, errors show the detailed developer exception page; in Production they go to /Home/Error.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. CreateBuilder sets up configuration, logging, Kestrel and DI.
  2. Services are registered on builder.Services before Build(); IProductCatalog will be injected wherever it is requested.
  3. The pipeline is configured on app: production-only middleware first, then HTTPS redirection, routing and authorization.
  4. MapStaticAssets and the default controller route connect URLs to files and controllers.
  5. app.Run() blocks and serves requests until the process stops.

Real-world use case#

Every feature added to ShopSmart touches Program.cs once: AddDbContext for the database, AddIdentity for logins, AddSession, AddResponseCaching… and the matching Use… call in the right place.

Common mistakes#

Registering services after Build()

builder.Services is read-only after Build(); registering later throws “The service collection cannot be modified because it is read-only”.

Wrong middleware order

UseAuthorization before UseRouting, or UseAuthentication after UseAuthorization, silently breaks security.

Giant Program.cs

Dozens of registrations make it hard to read. Group them in extension methods like builder.Services.AddShopServices().

Best practices#

  • Keep the four phases visually separate
  • Wrap production-only middleware in an environment check
  • Move groups of registrations into extension methods
  • Use MapStaticAssets in .NET 9+ for cache-friendly static files

Remember this#

Services → Build → Pipeline → Endpoints → Run.

Register on builder.Services, configure on app.

IsDevelopment() decides error pages and HSTS.

Practice exercise#

Your task: Move the ShopSmart service registrations into an extension method builder.Services.AddShopServices() in a file Extensions/ServiceCollectionExtensions.cs, and call it from Program.cs.

Need a hint?
public static IServiceCollection AddShopServices(this IServiceCollection services) { … return services; }
Show solution
C# Illustrative
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddShopServices();
var app = builder.Build();
app.MapDefaultControllerRoute();
app.Run();


// Extensions/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddShopServices(this IServiceCollection services)
    {
        services.AddScoped<IProductCatalog, InMemoryProductCatalog>();
        return services;
    }
}


public interface IProductCatalog { IReadOnlyList<string> GetNames(); }
public class InMemoryProductCatalog : IProductCatalog
{
    public IReadOnlyList<string> GetNames() => ["Wireless Mouse"];
}

Mini quiz#

Program.cs quizMultiple choice Question 1 of 3

Where must services be registered?

Score so far: 0/0

Want more? Practise all ASP.NET Core MVC MCQs · ASP.NET Core MVC interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.