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.
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.
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"]}");
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.
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())}");
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.