Section 3 of 6

Program.cs Deep Dive

🎯 What You'll Learn

  • Understanding the Program.cs file structure
  • The WebApplicationBuilder and its purpose
  • Configuring services with dependency injection
  • The middleware pipeline and request processing
  • Understanding app.Run() and the hosting model
  • Common middleware and their order
  • Customizing your application startup
  • Best practices for Program.cs organization

The Default Program.cs

When you create a new Web API project, you get a Program.cs file that looks like this:

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

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

This file is the entry point of your application. Let's break it down line by line.

Part 1: Building the Application

Creating the Builder

Creating the Builder C#
var builder = WebApplication.CreateBuilder(args);

WebApplication.CreateBuilder(args) creates a WebApplicationBuilder that configures:

  • Configuration: Loads appsettings.json, environment variables, command-line args
  • Logging: Sets up default logging providers
  • Dependency Injection: Initializes the service container
  • Hosting: Configures Kestrel web server
💡 The Builder Pattern

The builder pattern lets you configure your application step by step before actually creating it. Think of it like configuring a car before it's built—you specify the engine, wheels, color, etc., then call Build() to assemble it.

Registering Services

Service Registration C#
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services is the dependency injection container. Here we're registering services:

Method What It Does
AddControllers() Registers MVC controllers and related services
AddEndpointsApiExplorer() Enables API endpoint discovery for Swagger
AddSwaggerGen() Registers Swagger/OpenAPI documentation generator

Building the Application

Build C#
var app = builder.Build();

Build() creates the WebApplication instance. After this point:

  • You can't add more services
  • You configure the middleware pipeline
  • You define how requests are processed

Part 2: Configuring the Middleware Pipeline

What is Middleware?

Middleware are components that form a pipeline to process HTTP requests and responses. Each middleware can:

  1. Process the incoming request
  2. Pass the request to the next middleware
  3. Process the outgoing response
  4. Short-circuit the pipeline (return a response immediately)
Middleware Pipeline Flow Text
Request → [Middleware 1] → [Middleware 2] → [Middleware 3] → Endpoint
            ↓              ↓              ↓              ↓
         Logging        CORS       Authentication   Controller
            ↑              ↑              ↑              ↑
Response ← [Middleware 1] ← [Middleware 2] ← [Middleware 3] ← Endpoint

Environment-Specific Middleware

Development-Only Middleware C#
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

This code only runs in Development environment. Swagger UI is useful for testing but shouldn't be exposed in production.

Common Middleware

Standard Middleware C#
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
Middleware Purpose
UseHttpsRedirection() Redirects HTTP requests to HTTPS
UseAuthorization() Enables authorization checks
MapControllers() Maps controller endpoints to routes
⚠️ Middleware Order Matters!

The order you add middleware is critical. For example:
UseAuthentication() must come before UseAuthorization()
UseRouting() must come before UseAuthorization()
UseCors() should be early in the pipeline

Starting the Application

Run C#
app.Run();

Run() starts the web server and begins listening for requests. This is a blocking call—your application runs until you stop it (Ctrl+C).

A More Complete Example

Here's a more realistic Program.cs for InvenTrack:

Program.cs (InvenTrack) C#
using Microsoft.EntityFrameworkCore;
using Serilog;

// Configure Serilog
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

try
{
    Log.Information("Starting InvenTrack API");

    var builder = WebApplication.CreateBuilder(args);

    // Add Serilog
    builder.Host.UseSerilog();

    // Add services to the container
    builder.Services.AddControllers();
    
    // Add database context
    builder.Services.AddDbContext<InvenTrackDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

    // Add CORS
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("AllowFrontend", policy =>
        {
            policy.WithOrigins("http://localhost:3000")
                  .AllowAnyMethod()
                  .AllowAnyHeader();
        });
    });

    // Add authentication
    builder.Services.AddAuthentication()
        .AddJwtBearer();

    // Add Swagger
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();

    // Register application services
    builder.Services.AddScoped<IProductService, ProductService>();
    builder.Services.AddScoped<IInventoryService, InventoryService>();

    var app = builder.Build();

    // Configure the HTTP request pipeline
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }

    // Middleware pipeline (order matters!)
    app.UseSerilogRequestLogging();
    app.UseHttpsRedirection();
    app.UseCors("AllowFrontend");
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapControllers();

    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
    Log.CloseAndFlush();
}

Understanding Service Lifetimes

When registering services, you choose a lifetime:

Transient

Transient C#
builder.Services.AddTransient<IEmailService, EmailService>();
  • Created every time they're requested
  • Best for lightweight, stateless services
  • Example: Email sender, PDF generator

Scoped

Scoped C#
builder.Services.AddScoped<IProductService, ProductService>();
  • Created once per HTTP request
  • Best for services that work with databases
  • Example: Repository, DbContext, business services

Singleton

Singleton C#
builder.Services.AddSingleton<ICacheService, CacheService>();
  • Created once for the application lifetime
  • Shared across all requests
  • Must be thread-safe!
  • Example: Configuration, cache, logging
💡 Choosing a Lifetime

Default choice: Use Scoped for most services
Stateless/lightweight: Use Transient
Expensive to create: Use Singleton (if thread-safe)
DbContext: Always Scoped

Common Middleware in Order

Here's the recommended middleware order for most applications:

Recommended Middleware Order C#
// 1. Exception handling (catch errors early)
app.UseExceptionHandler("/error");

// 2. HSTS (HTTP Strict Transport Security)
app.UseHsts();

// 3. HTTPS redirection
app.UseHttpsRedirection();

// 4. Static files (if serving HTML/CSS/JS)
app.UseStaticFiles();

// 5. Routing
app.UseRouting();

// 6. CORS (must be after UseRouting, before UseAuthentication)
app.UseCors("MyPolicy");

// 7. Authentication (who are you?)
app.UseAuthentication();

// 8. Authorization (what can you do?)
app.UseAuthorization();

// 9. Custom middleware
app.UseMiddleware<RequestTimingMiddleware>();

// 10. Endpoints (controllers, minimal APIs)
app.MapControllers();

Custom Middleware

You can create custom middleware for cross-cutting concerns:

Inline Middleware

Inline Middleware C#
app.Use(async (context, next) =>
{
    // Before the next middleware
    Console.WriteLine($"Request: {context.Request.Path}");
    
    // Call next middleware
    await next();
    
    // After the next middleware
    Console.WriteLine($"Response: {context.Response.StatusCode}");
});

Class-Based Middleware

RequestTimingMiddleware.cs C#
public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestTimingMiddleware> _logger;

    public RequestTimingMiddleware(
        RequestDelegate next, 
        ILogger<RequestTimingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();

        await _next(context);

        stopwatch.Stop();
        _logger.LogInformation(
            "Request {Method} {Path} took {ElapsedMilliseconds}ms",
            context.Request.Method,
            context.Request.Path,
            stopwatch.ElapsedMilliseconds
        );
    }
}

Register it in Program.cs:

Program.cs C#
app.UseMiddleware<RequestTimingMiddleware>();

Accessing Configuration

The builder provides access to configuration:

Reading Configuration C#
// Get connection string
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

// Get app setting
var apiKey = builder.Configuration["ApiKey"];

// Get section
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secret = jwtSettings["Secret"];

Environment Detection

Environment Checks C#
if (app.Environment.IsDevelopment())
{
    // Development-only code
}

if (app.Environment.IsProduction())
{
    // Production-only code
}

if (app.Environment.IsEnvironment("Staging"))
{
    // Staging-only code
}

Key Takeaways

  • Program.cs is the entry point of your application
  • WebApplicationBuilder configures services and settings
  • builder.Services is the dependency injection container
  • Service lifetimes: Transient, Scoped, Singleton
  • builder.Build() creates the WebApplication
  • Middleware forms a pipeline to process requests
  • Middleware order matters—follow the recommended sequence
  • app.Run() starts the web server
  • Use app.Environment to detect Development/Production
  • Custom middleware can be inline or class-based
  • Configuration is accessed via builder.Configuration
🎯 Next Steps

You now understand how ASP.NET Core applications start up and how the middleware pipeline works! In the next section, we'll dive into Configuration & appsettings.json— how to manage application settings, connection strings, and environment-specific configuration.