Skip to content

Expert · Web APIs with ASP.NET Core and EF Core · topic 31 of 43 · 35 min

ASP.NET Core: your first Web API

Explanation

ASP.NET Core is .NET's framework for web apps and HTTP APIs. A Web API receives HTTP requests (GET, POST, PUT, DELETE) and returns data, usually JSON. dotnet new webapi creates a project; minimal APIs map a URL to a lambda with app.MapGet and app.MapPost, while controllers group endpoints in classes. Both are used in companies.

What is a Web API?

When you open the Zomato app, the app on your phone does not contain the restaurant list. It sends an HTTP request over the internet to a server, and the server replies with data in JSON. That server program is a Web API. API means Application Programming Interface: a way for programs to talk to each other.

HTTP methodMeaningExample
GETRead dataGET /products/42
POSTCreate something newPOST /orders with the order in the body
PUTReplace / updatePUT /products/42 with the new data
DELETERemoveDELETE /cart/items/7
Think of it like this: A Web API is like a restaurant counter with a fixed menu. Customers (apps) do not walk into the kitchen. They order from the menu (the endpoints), in a fixed format (HTTP and JSON), and get back their food (data) or a polite "sorry, not available" (an error status).

Create and run a project

bash
dotnet new webapi -o ShopApi
cd ShopApi
dotnet run
The terminal shows the address it listens on, such as http://localhost:5xxx (the port is chosen per project). Press Ctrl + C to stop.

The template includes a sample /weatherforecast endpoint, a .http file you can use to send test requests from VS Code or Visual Studio, and OpenAPI support (a machine-readable description of your API). Recent templates generate the OpenAPI document but do not include a browsing UI by default; you can add one such as Swagger UI or Scalar, or test with the .http file, curl or Postman.

The smallest API

csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "ShopApi is running");
app.MapGet("/time", () => new { Now = DateTime.Now });

app.Run();
CreateBuilder sets up configuration, logging and the Kestrel web server. MapGet connects a URL to a handler. Returning an object sends it as JSON with status 200.
bash
curl http://localhost:5000/time
{"now":"2026-05-04T10:15:30.123+05:30"}
ASP.NET Core writes JSON in camelCase by default. Use your own port number.

A small in-memory product API

csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var products = new List<Product>
{
    new(1, "Steel bottle", 349m),
    new(2, "Cotton kurta", 799m)
};

app.MapGet("/products", () => products);

app.MapGet("/products/{id:int}", (int id) =>
    products.FirstOrDefault(p => p.Id == id) is Product p
        ? Results.Ok(p)
        : Results.NotFound());

app.MapPost("/products", (Product input) =>
{
    var created = input with { Id = products.Max(p => p.Id) + 1 };
    products.Add(created);
    return Results.Created($"/products/{created.Id}", created);
});

app.Run();

public record Product(int Id, string Name, decimal Price);
{id:int} is a route parameter that must be an int. The POST handler reads the JSON body into a Product. Results.Created returns status 201 with a Location header.
Careful: A List in memory is fine for learning but is lost when the app restarts and is not safe when many requests change it at once. Real APIs use a database (the EF Core topic) or a thread-safe store.

Controllers: the class-based style

Before minimal APIs (added in .NET 6), every ASP.NET Core API used controllers, and many company projects still do. A controller is a class whose public methods (actions) handle requests. Create a project with them using dotnet new webapi --use-controllers.

csharp
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]          // becomes api/products
public class ProductsController : ControllerBase
{
    private static readonly List<Product> Products = new() { new(1, "Steel bottle", 349m) };

    [HttpGet]
    public IEnumerable<Product> GetAll() => Products;

    [HttpGet("{id:int}")]
    public ActionResult<Product> GetById(int id)
    {
        var p = Products.FirstOrDefault(x => x.Id == id);
        return p is null ? NotFound() : Ok(p);
    }
}
Register them with builder.Services.AddControllers(); and app.MapControllers();. [ApiController] turns on helpful behaviour such as automatic validation errors.
Minimal APIsControllers
StyleLambdas mapped in Program.cs (or extension methods)Classes with action methods and attributes
Good forSmall services, microservices, quick APIsLarge APIs, teams used to MVC, existing codebases
Both supportRouting, DI, validation, auth, OpenAPIRouting, DI, validation, auth, OpenAPI

Common mistakes and fixes

  • Following an old tutorial with Startup.cs and ConfigureServices: that was the .NET Core 3 to 5 style. Since .NET 6 everything starts in Program.cs. The ideas are the same.
  • Forgetting app.Run(): the app builds and exits immediately.
  • Forgetting AddControllers and MapControllers: controllers exist but every URL returns 404.
  • Returning null for "not found": the client gets 204 or 200 with no body. Return Results.NotFound() / NotFound() so the client gets 404.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

ASP.NET Core: your first Web API · 1/7

ASP.NET Core Web API

In this lesson you will build your first Web API with ASP.NET Core.

Prefer a video by a teacher? Find free videos on “ASP.NET Core minimal API tutorial .NET 8 web api beginners” on YouTube ↗

In real life: A pincode lookup service for a delivery app

A delivery startup needs a tiny internal service: given a PIN code, return the city and whether they deliver there. A minimal API does it in a few lines, and the mobile app calls GET /serviceable/411001.

csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var areas = new Dictionary<string, string> { ["411001"] = "Pune", ["560001"] = "Bengaluru" };

app.MapGet("/serviceable/{pin}", (string pin) =>
    areas.TryGetValue(pin, out var city)
        ? Results.Ok(new { pin, city, deliver = true })
        : Results.NotFound(new { pin, deliver = false }));

app.Run();
GET /serviceable/411001 returns 200 with {"pin":"411001","city":"Pune","deliver":true}. An unknown PIN returns 404.

Practice (0/4 done)

Task 1 · Question

Which HTTP method is normally used to create a new order?

Task 2 · Question

What does app.MapGet("/hi", () => new { Msg = "Hi" }) return to the client?

Task 3 · Question

In "/products/{id:int}", what does :int do?

Task 4 · Practice

Create a minimal API project called NotesApi with an in-memory List<Note> (record Note(int Id, string Text)). Add GET /notes, GET /notes/{id:int} (404 if missing), POST /notes (201 Created) and DELETE /notes/{id:int} (204 No Content, or 404). Test every endpoint with the .http file or curl.

Quick quiz

Question 1

1.What is Kestrel?

Question 2

2.Which status code does Results.Created(...) send?

Question 3

3.What do you need for controllers to work?

Question 4

4.Since which version does a new ASP.NET Core app start from Program.cs without Startup.cs?

Question 5

5.By default, how does ASP.NET Core name JSON properties?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is Kestrel?

Answer: The web server built into ASP.NET Core

Kestrel is the cross-platform web server ASP.NET Core runs on.

Which status code does Results.Created(...) send?

Answer: 201

201 Created, with a Location header.

What do you need for controllers to work?

Answer: builder.Services.AddControllers() and app.MapControllers()

Controllers must be registered and mapped.

Since which version does a new ASP.NET Core app start from Program.cs without Startup.cs?

Answer: .NET 6

The minimal hosting model arrived in .NET 6.

By default, how does ASP.NET Core name JSON properties?

Answer: camelCase

ASP.NET Core uses the web defaults: camelCase.

For the route /api/users/:id and the URL /api/users/42, what is req.params.id?Related

Answer: The text "42"

Route params always arrive as strings. Convert with Number() when you need a number.

From Express: your first web server

Which file tells .NET the project's target version?Related

Answer: The .csproj file

The <TargetFramework> element in the .csproj, for example net10.0.

From Installing .NET and using the dotnet CLI

What does "stateless" mean for a REST API?Related

Answer: Each request carries everything needed, such as the token; the server does not remember earlier requests

Stateless means no memory of earlier requests, so every request includes its own authentication and data.

From REST APIs and JSON

Which should NOT be committed to your project repository?Related

Answer: bin/ and obj/ folders and secrets

Build output and secrets stay out of Git.

From Your portfolio project: a tested Web API with EF Core

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Web APIs with ASP.NET Core and EF Core” before its exam: Routing, model binding, validation and status codes, Dependency injection and service lifetimes, Configuration, secrets and the middleware pipeline, Entity Framework Core: databases with C#.
  2. Module 8 exam: Web APIs with ASP.NET Core and EF Core · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Testing, tooling and Unity. 43 topics left in the course before the final exam.

← Previous: Files and JSON with System.Text.Json