Lesson Weekend

In the last lesson, we added create and read functionality to our Cretaceous Park API. In this lesson, we'll add update and delete functionality as well.

Adding Update Functionality


We'll start by editing an animal. In order to do so, we'll use a new HTTP verb in our route called PUT. PUT is like POST in that it makes a change to the server. However, PUT changes existing information while POST creates new information. Here's our new PUT route:

Controllers/AnimalsController.cs
...
    // PUT: api/Animals/5
    [HttpPut("{id}")]
    public async Task<IActionResult> Put(int id, Animal animal)
    {
      if (id != animal.AnimalId)
      {
        return BadRequest();
      }

      _db.Entry(animal).State = EntityState.Modified;

      try
      {
        await _db.SaveChangesAsync();
      }
      catch (DbUpdateConcurrencyException)
      {
        if (!AnimalExists(id))
        {
          return NotFound();
        }
        else
        {
          throw;
        }
      }

      return NoContent();
    }
...
    private bool AnimalExists(int id)
    {
      return _db.Animals.Any(e => e.AnimalId == id);
    }
  }
}

Once again, this looks similar to code we'd use in a web application.

  • We use an [HttpPut] annotation. This specifies that we'll determine which animal will be updated based on the id parameter in the URL.

The code to actually update an animal should already be familiar. We use Entity's built-in properties and methods to modify the state of the animal and then save the changes. Remember, in order to use EntityState we'll have to include using Microsoft.EntityFrameworkCore if we aren't already.

We also create a private method, AnimalExists, for use within the controller, to DRY up our code.

Next, we can test our API endpoint using Postman.

We'll make a call to the following endpoint: http://localhost:5000/api/animals/1. This is exactly the same as the URL for getting an individual animal's detail. The difference is that we need to specify a PUT request instead of a GET request.

According to the HTTP specification, a PUT request requires the client to send the entire updated entity, not just the changes. To support partial updates, use PATCH. The body of the API call will look something like this:

{
    "animalId": 1,
    "species": "Woolly Mammoth",
    "name": "Matilda",
    "age": 3,
    "gender": "Female"
}

This specifies that an animal with an AnimalId property of 1 (as specified in the URL of http://localhost:5000/api/animals/1) should be updated so that it's now Matilda the Woolly Mammoth. We can confirm this by changing our PUT request into a GET request in Postman and then making another call. We should get our edited animal back.

Adding Delete Functionality


Now we're ready to add delete functionality. We'll be using a new HTTP verb and annotation for this method as well:

Controllers/AnimalsController.cs
...
    // DELETE: api/Animals/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteAnimal(int id)
    {
      var animal = await _db.Animals.FindAsync(id);
      if (animal == null)
      {
        return NotFound();
      }

      _db.Animals.Remove(animal);
      await _db.SaveChangesAsync();

      return NoContent();
    }
...

Note the new [HttpDelete] annotation. It takes an id as a URL parameter just like our equivalent GET and PUT methods.

Other than that, the code is the same as what we'd see in a web application. Entity doesn't care whether it gets information from an API or a web application when manipulating the database.

We can make our delete request by specifying the DELETE verb in Postman and making an API call to the following URL: http://localhost:5000/api/animals/1. This will delete Matilda the Woolly Mammoth, just like time did.

Ultimately, the main difference between update and delete methods in a web application versus an API is the annotation. Remember that forms in HTML5 don't allow for PUT, PATCH or DELETE verbs. For that reason, we had to use HttpPost along with an ActionName like this: [HttpPost, ActionName("Delete")]. However, we aren't using HTML anymore and there are no such limitations with an API. For that reason, we can use HttpPut and HttpDelete.

Also, note that we were able to have all CRUD functionality with only two URLs:

http://localhost:5000/api/animals

and

http://localhost:5000/api/animals/1

The benefits of RESTful standards become more readily apparent with an API. Developers don't need to search through documentation in order to surmise the correct URLs for an API. While a user of a web application might not notice that a URL in the browser is RESTful, a developer using an API certainly will notice whether the URL is RESTful and easy to work with. We should always keep the names of our endpoints as RESTful as possible.

Repository Reference

Follow the link below to view how a sample version of the project should look at this point. Note that this is a link to a specific commit in the repository.

Example GitHub Repo for Cretaceous Park

Scaffolding our Controller


You've now added full CRUD to your API! Although we want you to be comfortable writing the routes, in the future, you might find it easier to use a scaffolding tool to create the boilerplate code for your routes. In this section's code review, you should not be using a scaffolding tool to create the boilerplate code for your routes. We want to see an understanding of this material and an ability to create it on your own, without the use of this tool. However, past this code review and in personal projects, feel free to use this to spin up a project quickly!

Note that this tool is most effective after you have created your model and database context, but before you have created your controller. You should not run this now, because the AnimalsController already exists. However, if you want to test it out, you could delete your AnimalsController, and run the command to scaffold it again. You'll need to tweak the return value of Post, as we did last lesson. These are commands to reference in future projects.

First, we'll need to install some packages to our project, required for scaffolding:

$ dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 5.0.0
$ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design -v 5.0.0

Then, we'll install the tool aspnet-codegenerator, to create a controller based on a given model and database context. Note that we'll only ever need to run this once:

$ dotnet tool install -g dotnet-aspnet-codegenerator
$ dotnet tool update -g dotnet-aspnet-codegenerator

Finally, we can scaffold!

$ dotnet aspnet-codegenerator controller -name AnimalsController -async -api -m Animal -dc CretaceousParkContext -outDir Controllers

Lesson 6 of 22
Last updated April 6, 2022