█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Programming Languages/C# and .NET/ASP.NET Core REST APIs
70 minIntermediate

ASP.NET Core REST APIs

After this lesson, you will be able to: Build a REST API in ASP.NET Core with controllers or minimal APIs, routing, middleware, and dependency injection.

ASP.NET Core is the .NET web framework. Fastest mainstream web framework (per TechEmpower benchmarks). Built-in DI, middleware pipeline, OpenAPI support. The bread and butter of C# backend jobs.

Prerequisites:async/await in C#

Minimal API (recommended for new projects)

Lightweight; great for microservices.

tsx
// Create: dotnet new webapi -minimal -o myapi && cd myapi && dotnet run
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IUserStore, InMemoryUserStore>();
var app = builder.Build();
// Routes
app.MapGet("/", () => "Hello!");
app.MapGet("/users/{id:int}", async (int id, IUserStore store) => {
var user = await store.FindByIdAsync(id);
return user is null ? Results.NotFound() : Results.Ok(user);
});
app.MapPost("/users", async (User user, IUserStore store) => {
await store.SaveAsync(user);
return Results.Created($"/users/{user.Id}", user);
});
app.Run();
// User type
public record User(int Id, string Name, string Email);

Controller-based API (classic)

Preferred for big APIs with many routes.

tsx
// Controllers/UsersController.cs
[ApiController]
[Route("users")]
public class UsersController : ControllerBase {
private readonly IUserStore _store;
public UsersController(IUserStore store) {
_store = store;
}
[HttpGet("{id:int}")]
public async Task<ActionResult<User>> GetById(int id) {
var user = await _store.FindByIdAsync(id);
return user is null ? NotFound() : Ok(user);
}
[HttpPost]
public async Task<ActionResult<User>> Create(User user) {
await _store.SaveAsync(user);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<IUserStore, InMemoryUserStore>();
var app = builder.Build();
app.MapControllers();
app.Run();

Middleware pipeline

Request-response chain. Order matters.

tsx
var app = builder.Build();
// Exception handler, outermost
app.UseExceptionHandler("/error");
// HTTPS redirect
app.UseHttpsRedirection();
// CORS
app.UseCors("AllowAll");
// Auth
app.UseAuthentication();
app.UseAuthorization();
// Custom middleware (inline)
app.Use(async (context, next) => {
Console.WriteLine($"{context.Request.Method} {context.Request.Path}");
await next.Invoke();
Console.WriteLine($" → {context.Response.StatusCode}");
});
// Routes, innermost
app.MapControllers();
app.Run();

💡 DI is built in — use it

.NET's dependency injection container is first-class. Register services in builder.Services and inject anywhere via constructor. Three lifetimes: Singleton (one for app), Scoped (one per request, DEFAULT for EF Core DbContext), Transient (new every time). Wrong lifetime is a real bug, Singleton holding a Scoped DbContext = data corruption across requests.

Common mistakes

Wrong DI lifetime (Scoped vs Singleton vs Transient, read carefully). Skipping `AddCors` then puzzling over CORS errors. Forgetting `app.MapControllers()`, routes won't match. Putting business logic in controllers (extract to services).

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Async/Await in C#
Back to C# and .NET
Entity Framework Core→