What ASP.NET Core Is & Your First Server
You know C# - classes, records, async/await - and now you want to put
something on the web with it. ASP.NET Core is Microsoft's modern web framework: cross-platform, fast,
open source, and under an enormous share of the world's enterprise backends. If you write C# on the
server, this is the framework you'll meet.
The old .NET Framework was Windows-only and ceremony-heavy. This version runs anywhere, and since .NET 6 it offers minimal APIs that let you stand up a working server in a handful of lines - no sprawling boilerplate, no XML config. A request comes in, your function runs, a response goes out.
📝 This guide teaches the framework, and it assumes you already know C#. It pairs with What a Framework Even Is; its data layer is EF Core; and the server and pipeline underneath it are the roots guide The ASP.NET Pipeline & Kestrel. The examples here are regular C# you compile and run - not editable in the browser - so each one comes with the commands to run it yourself.
The mental model: a pipeline and an injector
Two ideas underlie everything else in ASP.NET Core - routing, validation, auth. Learn them now and the rest reads as detail rather than magic.
📝 First, a request flows through a middleware pipeline. It doesn't jump straight to your code - it travels through an ordered chain of components, each able to look at the request, do something, hand it to the next link, and then act on the response coming back. Logging, authentication, error handling: each is a link in that chain. (Phase 5 builds the pipeline properly.)
📝 Second, your code receives its dependencies through dependency injection. Instead of your code reaching out to create the things it needs - a database connection, a logger, a service - you register those in one place and the framework hands them to whatever asks. (Phase 4 covers DI in full.)
💡 Both pillars rest on Kestrel - the high-performance web server ASP.NET Core runs on - covered in The ASP.NET Pipeline & Kestrel. Now let's get a server running.
Your first server
Create a new project. From a terminal:
What just happened: dotnet new web scaffolded a minimal ASP.NET Core project into a folder called
MyApi (-o names the output folder). The template is deliberately tiny - no controllers, no extra
files, just a single Program.cs and a project file, which is your entire app's starting point. Open it
and you'll find something close to this:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello from ASP.NET Core");
app.Run();
What just happened: four lines, and that's a complete web server. Top to bottom:
WebApplication.CreateBuilder(args)creates a builder - wires up configuration, logging, and the dependency injection container you'll later register services in.argsare command-line arguments, passed through so flags can override config.builder.Build()takes everything the builder set up and produces the finishedapp- aWebApplication. The builder configures;Build()seals it into a runnable app.app.MapGet("/", () => "...")registers an endpoint: when aGETrequest arrives for path/, run this lambda.app.Run()starts Kestrel (the server) and begins listening. This call blocks - the program parks here handling requests until you stop it. Set up anything you need before callingRun().
Now run it:
$ dotnet run
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
What just happened: dotnet run compiled and launched your project. app.Run() brought Kestrel up,
and it's now listening (the exact port may differ on your machine - read it from the log). Leave it
running, and in another terminal hit the endpoint:
$ curl http://localhost:5000/
Hello from ASP.NET Core
What just happened: curl sent a GET /. The request entered the pipeline, reached your MapGet
endpoint, the lambda ran, and its return value came back as the response body. You have a working web
server in four lines of real code. Press Ctrl+C to stop it.
Returning text vs. returning an object
The handler above returned a plain string, and you got plain text back. ASP.NET Core looks at what
your handler returns and does the sensible thing: a string gets you text, an object gets serialized to
JSON automatically.
This guide builds toward a small products API. First, a type to represent a product - a C# record,
perfect for this kind of immutable data:
record Product(int Id, string Name, decimal Price);
What just happened: that one line declares a Product with three properties. A positional record
gives you the constructor, the properties, and value-based equality for free - exactly what you want for
a data shape that flows in and out as JSON. This Product is what we'll spend the next eight phases
turning into a full REST API.
Add a second endpoint that returns one:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello from ASP.NET Core");
app.MapGet("/products/sample", () => new Product(1, "Keyboard", 49.99m));
app.Run();
record Product(int Id, string Name, decimal Price);
What just happened: the new MapGet returns a Product object instead of a string. Because it's an
object, ASP.NET Core serializes it to JSON and sets the Content-Type header to application/json for
you - no manual serialization, no header fiddling. (The record sits at the bottom because in a
top-level Program.cs, type declarations come after executable statements.) Run it and ask for the
sample:
$ curl http://localhost:5000/products/sample
{"id":1,"name":"Keyboard","price":49.99}
What just happened: the object came back as clean JSON, with property names lower-cased the way JSON conventionally wants them. Two endpoints, two return types, zero extra plumbing.
💡 When you need to control the status code too - a 200 OK with a body, a 404 Not Found, a
201 Created - reach for the Results helpers: Results.Ok(product), Results.NotFound(), and
friends. We lean on those heavily once we build real CRUD (Phases 2 and 6). For now, returning a value
directly is the quickest way to see data flow.
Next: routing - turning one / endpoint into a real set of paths with route and query parameters.
Recap
- ASP.NET Core is Microsoft's modern web framework - cross-platform, fast, open source, and under a huge share of enterprise backends.
- Two pillars: a request flows through a middleware pipeline (Phase 5), and your code receives services via dependency injection (Phase 4). Both rest on Kestrel, the server.
- A whole app starts in
Program.cs.CreateBuilder(args)sets up config, logging, and DI;builder.Build()produces theapp;app.MapGet(...)registers an endpoint;app.Run()starts Kestrel and blocks. - CLI:
dotnet new web -o MyApiscaffolds it,dotnet runcompiles and launches it,curlhits it. - Return type decides the response: a
stringsends text; an object auto-serializes to JSON. UseResults.Ok(...)/Results.NotFound()for explicit status. - Running example: a products API, built on
record Product(int Id, string Name, decimal Price).
Quick check
Three questions on the ideas that have to stick - what ASP.NET Core is, the two pillars, and how a first server fits together:
[
{
"q": "In a minimal API Program.cs, what does `WebApplication.CreateBuilder(args)` set up?",
"choices": [
"Configuration, logging, and the dependency injection container",
"Only the route table for your endpoints",
"The database schema and connection pool",
"An HTML template engine for rendering pages"
],
"answer": 0,
"explain": "CreateBuilder produces the builder, which wires up configuration, logging, and the DI container. builder.Build() then turns that into the runnable app, and app.Run() starts Kestrel."
},
{
"q": "What are the two pillars of ASP.NET Core's mental model?",
"choices": [
"Requests flow through a middleware pipeline, and your code receives dependencies via dependency injection",
"A model layer and a view layer, like classic MVC only",
"Synchronous controllers and asynchronous background jobs",
"A compiler step and an interpreter step"
],
"answer": 0,
"explain": "Hold those two ideas and the rest is detail: a request travels an ordered middleware pipeline (Phase 5) to your endpoint, and the framework injects the services your code asks for (Phase 4)."
},
{
"q": "An endpoint handler returns `new Product(1, \"Keyboard\", 49.99m)`. What does the client receive?",
"choices": [
"JSON, because ASP.NET Core auto-serializes returned objects and sets the Content-Type",
"Plain text containing the object's type name",
"An error, because handlers must return a string",
"An empty 204 No Content response"
],
"answer": 0,
"explain": "Returning a string sends text; returning an object auto-serializes to JSON with Content-Type application/json. To control the status code explicitly, use the Results helpers like Results.Ok(...)."
}
]
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 3 questions
1. In a minimal API Program.cs, what does `WebApplication.CreateBuilder(args)` set up?
2. What are the two pillars of ASP.NET Core's mental model?
3. An endpoint handler returns `new Product(1, "Keyboard", 49.99m)`. What does the client receive?