By the end of this session, you will be able to:
dotnet new webapiTasksController with GET and POST endpoints using in-memory data| Time | Segment | Type | Duration |
|---|---|---|---|
| 0:00 | Tour of the generated project | Demo | 20 min |
| 0:20 | Middleware pipeline explained | Theory | 15 min |
| 0:35 | Layered architecture — the why | Theory | 20 min |
| 0:55 | Dependency injection in .NET | Theory + Demo | 25 min |
| 1:20 | Break | — | 10 min |
| 1:30 | Build the first TasksController | Lab | 40 min |
| 2:10 | Code review & discussion | Discussion | 20 min |
dotnet new webapi creates and understand what each one does before writing a single line of your own codeProgram.cs — the entry pointappsettings.json and launchSettings.jsonTaskManagerApi.Api/
├── Controllers/
│ └── WeatherForecastController.cs # delete this — sample only
├── Properties/
│ └── launchSettings.json # controls how the app starts locally
├── appsettings.json # configuration values
├── appsettings.Development.json # dev overrides (gitignored by default)
├── Program.cs # entry point — services + pipeline wired here
└── TaskManagerApi.Api.csproj # project file, NuGet package references
WeatherForecastController.cs and its model. It is a scaffold sample with no relation to your project.
app.MapControllers() — connects incoming URLs to controller action methodsapp.Run() — starts the HTTP server and blocks until the process exitsProgram.cs with no Startup class. Everything is in one file.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
app.UseXxx() in Program.cs is the order they executeTaskService without an HTTP server or a database. This is covered fully in Session 17.TaskManagerApi.Api/
├── Controllers/ # HTTP layer — one file per resource
├── Services/
│ └── Interfaces/ # ITaskService.cs lives here
├── Repositories/
│ └── Interfaces/ # ITaskRepository.cs lives here
├── Models/
│ ├── Entities/ # TaskItem.cs — the database model
│ └── DTOs/ # request / response shapes (introduced in Session 11)
└── Program.cs
Program.csnew TaskService() — it is tightly coupled, impossible to swap, and impossible to test in isolationnewIServiceCollection — you describe every service once in Program.cs, then the framework resolves them automaticallyITaskService rather than TaskService means you can substitute a different implementation (e.g. a test double) without touching the controllerHow long does the container keep an instance alive?
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<ITaskService, TaskService>();
builder.Services.AddScoped<ITaskRepository, TaskRepository>();
var app = builder.Build();
// ... pipeline config below
The pattern is always: AddScoped<IInterface, ConcreteClass>(). The container maps the interface to the implementation — callers only ever see the interface.
builder.Build(). After that the container is sealed and no further registrations are possible.
public class TasksController : ControllerBase
{
private readonly ITaskService _taskService;
public TasksController(ITaskService taskService)
{
_taskService = taskService;
}
}
new TaskService() — it does not know the concrete type existsTasksController with in-memory data. No database yet — that arrives in Session 09.TaskItem entityGET /api/tasks — return all tasksGET /api/tasks/{id} — 404 if not foundPOST /api/tasks — 201 with Location headerpublic class TaskItem
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public bool IsCompleted { get; set; }
public DateTime CreatedAt { get; set; }
}
static List<TaskItem> inside your service for this session. This is replaced by Entity Framework Core and a real database in Session 09.
Location header pointing to the new resource
201 with Location: /api/tasks/{newId}
// 200 OK — body serialised to JSON automatically
return Ok(tasks);
// 201 Created — sets Location header to /api/tasks/{id}
return CreatedAtAction(nameof(GetById), new { id = task.Id }, task);
// 404 Not Found — empty body
return NotFound();
// 400 Bad Request — empty body or custom message
return BadRequest();
All these helper methods live on ControllerBase — your controller inherits them automatically.
[ApiController] — enables automatic model validation, binding, and problem-detail error responses[Route("api/[controller]")] — sets the base URL; [controller] resolves to the class name minus the Controller suffix[HttpGet] / [HttpPost] — bind an action to an HTTP method"{id}" in the route template maps to the int id parameter by name[ApiController]
[Route("api/[controller]")]
public class TasksController : ControllerBase
{
// GET /api/tasks
[HttpGet]
public IActionResult GetAll() { ... }
// GET /api/tasks/42
[HttpGet("{id}")]
public IActionResult GetById(int id) { ... }
// POST /api/tasks
[HttpPost]
public IActionResult Create([FromBody] TaskItem task) { ... }
}
ITaskService) rather than depending directly on TaskService?"
dotnet new webapi scaffolds a working project — Program.cs is the single entry point where services are registered and the middleware pipeline is configuredOk(), NotFound(), CreatedAtAction() are helper methods on ControllerBase that produce semantically correct HTTP responsesHTTP Methods & Status Codes
TasksController with PUT, PATCH, and DELETE endpointsTasksController GET and POST endpoints pass all acceptance criteria before Session 04.
Create a new .NET Web API project called BooksApi that applies the layered architecture and dependency injection patterns from this session.
Controllers/, Services/Interfaces/, Repositories/Interfaces/, Models/Entities/Book entity with Id (int), Title (string), Author (string), and Year (int)IBooksService interface with GetAll() and GetById(int id), and a concrete in-memory BooksService implementationBooksController with GET /api/books and GET /api/books/{id} (404 if not found)BooksService registered as Scoped in Program.cs and injected into the controller via constructorPOST /api/books endpoint that creates a new book and returns 201 Created with a Location header pointing to the new resource.