Building Your First ASP.NET Core Web Application

HTTP Request Processing Overview

Every ASP.NET Core application handles client traffic through a centralized processing pipeline. When a browser sends an HTTP request, the framework routes it through a chain of middleware components. Each component can inspect, modify, or terminate the request before forwarding it to a terminal endpoint. The resulting response travels backward through the same chain, ultimately reaching the client. This modular design ensures predictable request handling and simplifies cross-cutting concerns like authentication, logging, and static asset delivery.

Project Initialization

You can scaffold a new web application using Visual Studio or the .NET Command-Line Interface. In the IDE, select the C# filtered templates, choose the ASP.NET Core Web App option, and define your solution boundaries. Alternatively, execute the following CLI commands to generate a standalone project:

dotnet new sln -n HostingApp
dotnet new webapp -o HostingApp
dotnet sln add HostingApp

The generated solution includes a project descriptor that defines the build system and runtime target:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>

This file instructs the compiler which libraries to reference and determines compatibility during deployment.

Application Bootstrap: Program.cs

The entry point orchestrates host initialization, server binding, and service resolution. Modern templates encapsulate much of this logic, but understanding the underlying sequence remains essential:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();

app.Run();

The CreateBuilder method configures a default host that automatically scans configuration providers, initializes logging, and allocates the dependency injection container. Calling Build() materializes the application factory, while Run() attaches the Kestrel web server to network interfaces and begins accepting inbound traffic.

Service Registration and Pipeline Construction

Although contemporary defaults often merge configuration into Program.cs, the architecture follows a two-phase pattern: dependency resolution followed by request routing. Services must be explicitly added to the IoC container before they become available at runtime:

public void RegisterServices(IServiceCollection collection)
{
    collection.AddRazorPagesOptions(options =>
    {
        options.Conventions.AddAreaFolderPageRoute("Admin", "/Dashboard", "/Index");
    });
    collection.AddDbContext<DataContext>(options =>
        options.UseSqlServer(connectionString));
}

Middleware arrangement dictates execution order. Components process requests sequentially, and earlier stages can bypass later ones. A standard ordering prioritizes security and routing before final dispatch:

  • StaticFileMiddleware: Intercepts requests mapped to disk assets (stylesheets, scripts, media). If a match exists, it returns the resource immediately.
  • RoutingMiddleware: Parses the URL structure to identify compatible endpoints.
  • AuthorizationMiddleware: Enforces access policies and validates claims.
  • EndpointMiddleware: Invokes the matched handler, such as a controller action or Razor view.

Environment-Aware Configuration

Applications frequently require divergent behavior across deployment stages. The IWebHostEnvironment abstraction exposes runtime context, enabling conditional pipeline adjustments:

switch (app.Environment.EnvironmentName)
{
    case "Development":
        app.UseStatusCodePagesWithReExecute("/Errors/{0}");
        break;
    case "Staging":
        app.UseMiddleware<PerformanceMonitoringMiddleware>();
        break;
    case "Production":
        app.UseExceptionHandler("/GeneralFailure");
        app.UseHsts(maxAge: TimeSpan.FromDays(365));
        break;
}

During local development, diagnostic pages surface exception traces, query string data, and header metadata to accelerate debugging. Deployed environments suppress this exposure, delivering controlled fallback views that maintain user experience without leaking implementation details.

Razor Views and Code-Behind Separation

Dynamic page rendering relies on Razor templates stored under the Pages directory. Every routable view must declare the @page directive at the topmost position to register itself within the endpoint mapper.

@page
@model OverviewModel
@{
    Layout = "_Layout";
    ViewData["Header"] = "System Status";
}
<section class="content">
    <h2>@ViewData["Header"]</h2>
    <p>Current uptime: @Model.SystemUptime.Minutes minutes.</p>
</section>

The @model directive binds the markup to a dedicated code-behind class. Convention places this companion file adjacent to the template with a .cs suffix:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace HostingApp.Pages
{
    public class OverviewModel : PageModel
    {
        private readonly ILogger<OverviewModel> _tracer;
        private readonly IStatusProvider _monitor;

        public OverviewModel(ILogger<OverviewModel> tracer, IStatusProvider monitor)
        {
            _tracer = tracer;
            _monitor = monitor;
        }

        public TimeSpan SystemUptime { get; private set; }

        public async Task OnGetAsync()
        {
            var metrics = await _monitor.CollectMetricsAsync();
            SystemUptime = metrics.CurrentSessionDuration;
            _tracer.LogInformation("Overview page accessed");
        }
    }
}

Razor Pages decouple presentation from business logic. Handler methods follow verb-based naming conventions (OnGet, OnPost, OnPut). A void return triggers template compilation, while IActionResult types enable explicit redirects or status code modifications. Constructor injection guarantees that required dependencies are instantiated by the framework prior to handler execution.

End-to-End Request Trace

When a user navigates to /Overview, the request traverses the middleware stack. Static checks fail, routing resolves the path to Overview.cshtml, and authorization verifies permissions. Control transfers to OverviewModel.OnGetAsync(), where injected services fetch live metrics. The completed model state populates the Razor buffer, which streams the compiled HTML back through the reverse chain until Kestrel transmits it to the requesting client. This disciplined flow supports horizontal scaling, facilitates unit testing, and isolates infrastructure changes from domain logic.

Tags: asp.net-core razor-pages middleware-pipeline dependency-injection kestrel-server

Posted on Thu, 21 May 2026 20:57:07 +0000 by Zay