1.1: Getting started with HTTP APIs
“Out of the box”, Autobarn only exposes data via a web interface. To view or edit vehicle data, you have to use a web browser, and data is returned in HTML format – which is cool for humans who like reading stuff, but if you’re trying to build your own software to interact with Autobarn’s data, HTML isn’t a great format choice.
We’re going to create an HTTP API so that folks out there on the internet can build their own software tools that connect to Autobarn.
Our first API endpoint
The first endpoint we’re going to create will return a list of every vehicle in the system.
Create a new folder in Autobarn.Website\Controllers called Api.
Add a new class in this folder called VehiclesController.cs
using Autobarn.Data;
using Autobarn.Data.Entities;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace Autobarn.Website.Controllers.Api {
[Route("api/[controller]")]
[ApiController]
public class VehiclesController : ControllerBase {
private readonly AutobarnDbContext db;
public VehiclesController(AutobarnDbContext db) {
this.db = db;
}
// GET: api/vehicles
[HttpGet]
public IEnumerable<Vehicle> Get() => db.Vehicles.ToList();
}
}
There’s a bunch of interesting stuff going on here - but the remarkable thing, really, is how much we’ve done with relatively few lines of code.
The class is decorated with two attributes:
[ApiController]wires up a bunch of default behaviour for binding parameters, like automatically converting JSON requests into .NET objects.[Route("api/[controller]")]makes the new controller available at/api/vehicles
We’re using dependency injection to inject our AutobarnDbContext into our class, and then creating a single method called Get()
All our method does is return db.Vehicles().ToList(): ASP.NET will serialize the result into JSON for us and set the appropriate headers on the response.
Adding More Controller Endpoints
First, we’ll add a method to our controller which lets use an HTTP POST to add new vehicles to our database:
// POST api/vehicles
[HttpPost]
public async Task<IActionResult> Post([FromBody] VehicleDto dto) {
var vehicleModel = db.FindModel(dto.ModelCode);
var vehicle = new Vehicle {
Registration = dto.Registration,
Color = dto.Color,
Year = dto.Year,
VehicleModel = vehicleModel
};
db.Vehicles.Add(vehicle);
await db.SaveChangesAsync();
return Ok(vehicle);
}
Next, we’ll add a method that uses HTTP PUT to create or update a vehicle in the database.
The semantics of HTTP PUT can be a little confusing. I tend to think of it as “here is a resource: PUT the resource at this address, whether it already exists or not.”
// PUT api/vehicles/ABC123
[HttpPut("{id}")]
public IActionResult Put(string id, [FromBody] VehicleDto dto) {
var vehicleModel = db.FindModel(dto.ModelCode);
var vehicle = new Vehicle {
Registration = dto.Registration,
Color = dto.Color,
Year = dto.Year,
ModelCode = vehicleModel.Code
};
db.UpdateVehicle(vehicle);
return Ok(dto);
}
Finally, we’ll add a method that uses HTTP DELETE to remove a vehicle from the database:
// DELETE api/vehicles/ABC123
[HttpDelete("{id}")]
public IActionResult Delete(string id) {
var vehicle = db.FindVehicle(id);
if (vehicle == default) return NotFound();
db.DeleteVehicle(vehicle);
return NoContent();
}
Exercise: Building HTTP APIs
Create a new controller at Autobarn.Website/Controllers/Api/ModelsController.cs
Implement HTTP endpoint methods for each of the following:
-
GET /api/modelsshould return a list of all vehicle models in the system -
GET /api/models/{id}should return the specific vehicle model identified by model code, e.g./api/models/dmc-delorean. If no matching vehicle model exists, return404 -
POST /api/modelsshould add a new model to the system.- You will need to include the manufacturer name and model name in the request body.
-
What should you do if the manufacturer does not exist?