The Program.cs for an MVC application configures two complementary phases: the service registration phase (what services are available) and the middleware pipeline phase (how requests flow through the application). The key differences from a Web API Program.cs are the use of AddControllersWithViews() (registers Razor view engine), UseStaticFiles() (serves wwwroot content), and MapControllerRoute() (conventional routing instead of attribute routing). Getting these right — including their order — determines whether CSS loads, views render, and sessions work correctly.
Full MVC Program.cs
// ── Build phase: register all services ───────────────────────────────────
var builder = WebApplication.CreateBuilder(args);
// Core MVC services — registers controllers, views, and Razor view engine
builder.Services.AddControllersWithViews(options =>
{
// Global action filters applied to all controllers
options.Filters.Add<ValidateModelStateFilter>();
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); // CSRF on all POSTs
});
// Session (server-side per-user state)
builder.Services.AddDistributedMemoryCache(); // required by AddSession
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict;
});
// Response compression (for large HTML pages)
builder.Services.AddResponseCompression(opts =>
opts.EnableForHttps = true);
// Application services
builder.Services.AddScoped<IPostService, PostService>();
builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
var app = builder.Build();
// ── Run phase: configure middleware pipeline ───────────────────────────────
// Exception handling (first — catches all downstream exceptions)
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseResponseCompression(); // compress responses (before UseStaticFiles)
app.UseStaticFiles(); // serve wwwroot — before routing for short-circuiting
app.UseRouting();
app.UseAuthentication(); // if using identity
app.UseAuthorization();
app.UseSession(); // must be after routing, before MapControllerRoute
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
AddSession() requires a distributed cache provider registered first — AddDistributedMemoryCache() for single-server in-memory sessions or AddStackExchangeRedisCache() for multi-server Redis-backed sessions. Session data is not inherently encrypted; configure cookie settings carefully. UseSession() must be placed after UseRouting() so it runs in the request context, and before any middleware or controllers that need session data. Sessions are identified by a cookie sent to the browser — if the cookie is blocked or cleared, the session is lost.AutoValidateAntiforgeryTokenAttribute as a global filter to protect all POST, PUT, DELETE, and PATCH actions against CSRF attacks automatically. This is more reliable than remembering to add [ValidateAntiForgeryToken] to every form-submitting action. Pair it with the asp-antiforgery="true" Tag Helper (automatically applied when using <form asp-controller="..." asp-action="...">) to ensure the CSRF token is included in every form submission.UseStaticFiles() relative to UseRouting() and UseAuthentication() is significant. UseStaticFiles() short-circuits the pipeline for static file requests — they are served without going through routing or authentication. This is correct for public assets (CSS, JS, images) but means you cannot protect static files with [Authorize]. For protected files (user-uploaded content that requires authentication), serve them through a controller action rather than as static files in wwwroot.Static Files Configuration
// ── UseStaticFiles serves wwwroot by default ───────────────────────────────
app.UseStaticFiles();
// ── Serve files from a different directory ────────────────────────────────
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "StaticAssets")),
RequestPath = "/assets", // /assets/file.jpg → StaticAssets/file.jpg
OnPrepareResponse = ctx =>
{
// Cache static assets for 1 year in browser cache
ctx.Context.Response.Headers["Cache-Control"] = "public, max-age=31536000";
}
});
// ── Default files (serve index.html for directory requests) ──────────────
app.UseDefaultFiles(); // must be BEFORE UseStaticFiles
app.UseStaticFiles();
Common Mistakes
Mistake 1 — UseSession before UseRouting (session not in request context)
❌ Wrong — session middleware cannot access route data; session setup incomplete.
✅ Correct — UseSession after UseRouting and after UseAuthentication/UseAuthorization.
Mistake 2 — Not calling UseStaticFiles (CSS and JS return 404)
❌ Wrong — omitting UseStaticFiles means all wwwroot requests return 404.
✅ Correct — always include UseStaticFiles in MVC applications that serve static assets.