Course outline

ASP.NET Core / Basic: HTTP and Application Composition / Lesson 1

Hosting and the HTTP Contract

Separate the host, server, request pipeline, and application contract before building an endpoint.

About 5 min readContent reviewed
On this page

Lesson goals

  • Explain how an HTTP request becomes an HttpContext.
  • Distinguish service registration from pipeline composition.
  • Inspect synthetic requests without starting a server.

The host and application have different responsibilities

ASP.NET Core composes a host, an HTTP server, services, middleware, and endpoints. The host manages application lifetime and configuration; a server such as Kestrel handles connections and presents requests to the application. Middleware and endpoints implement behavior over HttpContext. C# language syntax alone does not determine routing, dependency lifetimes, or HTTP semantics.

A modern WebApplication builder offers convenient defaults, but defaults still read configuration and choose behavior. Record target framework, SDK, environment, and explicit overrides. This course uses in-memory component examples rather than launching a host, so it does not imply that a port, certificate, proxy, or deployed application was tested.

A request context contains several independent inputs

An HTTP request has a method, target path/query, headers, and possibly a body. HttpContext also exposes connection features, selected endpoint, user, scoped services, cancellation, and response state. Values can originate from different trust boundaries: a forwarded header is not automatically a trusted client fact, and a route value is not proof of authorization.

The first complete program constructs a synthetic request and reads its method, path, and query. It uses framework objects but no listener or client. The output verifies those assigned values only; it does not exercise URL parsing at a network boundary or a reverse proxy.

csharp Inspect an assigned in-memory request
using System;
using Microsoft.AspNetCore.Http;

var context = new DefaultHttpContext();
context.Request.Method = HttpMethods.Get;
context.Request.Path = "/reports";
context.Request.QueryString = new QueryString("?page=2");
Console.WriteLine($"{context.Request.Method} {context.Request.Path} page={context.Request.Query["page"]}");
Expected output
GET /reports page=2

DefaultHttpContext reports the synthetic method/path/query; no HTTP server receives traffic.

Status, headers, and body form one response contract

Choose status codes from the operation: success, invalid input, absence, authentication, authorization, conflict, and server failure are different outcomes. Set content type and headers before the response is committed. Returning JSON with 200 for every failure can force callers to guess whether an operation succeeded and can mislead monitoring or caches.

The second program writes a fixed response into a MemoryStream and inspects status and body. This verifies application-level assignment and writing. Default in-memory features do not reproduce every Kestrel header-commit or transport behavior, so the example must not be used as proof of real wire semantics.

csharp Inspect an in-memory response
using System;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Http;

var context = new DefaultHttpContext();
using var body = new MemoryStream();
context.Response.Body = body;
context.Response.StatusCode = StatusCodes.Status202Accepted;
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync("queued");
Console.WriteLine($"{context.Response.StatusCode} {Encoding.UTF8.GetString(body.ToArray())}");
Expected output
202 queued

The assigned 202 response contains queued in its local body stream; no network response is sent.

Registering a service does not execute a request stage

Service registration describes how dependencies are constructed. Pipeline composition describes which middleware runs and in what order. Endpoint mapping describes candidate operations and metadata. A service added to DI is not automatically middleware, and an endpoint declaration is not a background process. Keep these three forms of configuration distinct.

Application startup should validate required configuration before accepting work. Avoid constructing an extra service provider during registration or starting unowned tasks in a constructor. Such shortcuts can duplicate singletons, capture the wrong scope, or outlive shutdown. Later lessons develop the relevant lifetime contracts.

A local component check has a defined evidence boundary

Record what an example actually exercises: framework object, delegate, route pattern, policy, host integration, network, proxy, or database. Direct invocation is valuable for deterministic checks, but it does not test binding, authentication middleware, or provider translation unless those components are included. A passing console program is not a deployed API certificate.

For reproducibility, preserve source, project properties, shared-framework/SDK versions, synthetic input, and expected output. Use a temporary isolated project with no external package sources. Configuration fragments and unexecuted application setup stay non-runnable, and deployment requires its own authorized process rather than following from a successful lesson.

Common errors and fixes

A DI registration is expected to handle requests by itself.

Why it happens
Construction and pipeline execution were conflated.
How to fix it
Compose the relevant middleware/endpoint explicitly.
Prevent it next time
Review registration, pipeline, and mapping separately.

Every error is returned as 200.

Why it happens
Transport outcome and domain payload disagree.
How to fix it
Define status and body contracts for each failure category.
Prevent it next time
Test both status and content.

A direct context test is called a deployed HTTP test.

Why it happens
Validation scope was overstated.
How to fix it
State which framework layers were actually invoked.
Prevent it next time
Keep component and integration evidence separate.

Check your understanding

1. An endpoint returns a queued-work acknowledgment. Why may 202 be more honest than a body saying finished with 200?

Show worked solution

If the operation accepted work but has not completed the promised business effect,202 can communicate that asynchronous acceptance under a documented follow-up contract. Define the operation identifier, status lookup, retry/idempotency, and failure reporting. A status code alone does not build those mechanisms, and an application must not claim completion merely because it enqueued a record.

2. What additional evidence would be needed beyond the in-memory response example to verify proxy-facing behavior?

Show worked solution

Exercise an authorized isolated host/proxy path with actual headers, scheme/host handling, status commitment, connection behavior, and application expectations. Confirm trusted forwarded-header configuration and TLS termination. The local stream test checks assigned status and text only; it cannot prove what a real client sees or which external headers the application should trust.

Key takeaways

  • Host, server, services, middleware, and endpoints are distinct boundaries.
  • HTTP status and payload must describe the same outcome.
  • Match every validation claim to the layers actually tested.

Official references