Software Development

Adding more character methods to my API

Or continuing progress on my D&D API

17 April 2026

It has been a while since I worked on my Dungeons and Dragons API. It's time I got back to it. In my previous post on this project, I showed how I set up the first call to the database that my API would make, which was a method to get all the characters saved in the database. Now I wanted to finish of the CharacterController.cs file. There are four more things I need this controller to do:

  • Get a specific character based on a given ID
  • Create a new character
  • Update a character in the database
  • Delete a character in the database

Let's start with getting a specific character. This is very similar to getting all characters. It starts of with the same HttpGet method as before but this time we include an input value which we'll title id

[HttpGet("{id}")]

Given that we have an input for this method, the signature of the method will still be different even if we call it Get like the previous method.

public async Task<IActionResult> Get(int id)
{
    var character = await _getCharacterById.Get(id);
    return Ok(character);
}

Here this method calls another class called GetCharacterById. Again, I created this class in the Getters folder since it also is just getting data from the database.

public class GetCharacterById : IGetCharacterById
{
    private readonly ToffeeContext _context;

    public GetCharacterById(ToffeeContext context)
    {
        _context = context;
    }

    public async Task<Character> Get(int id)
    {
        var character = await _context.Characters.FindAsync(id);
        if (character == null)
            throw new KeyNotFoundException($"Character with id {id} not found.");
        return character;
    }
}

This follows much of the same pattern as the class that gets all the characters with a few important differences. First, the obvious one is that it finds just one entry from the database based on what ID is given and as a result it doesn't return a list but just returns the single database entry. There is also error handling for when the ID that is given doesn't match any database characters.

The next method to implement is to facilitate the creation of new characters. This now requires a HttpPost method.

[HttpPost]
public async Task<IActionResult> Post([FromBody] CreateCharacterRequest request)
{
    var character = await _createNewCharacter.Create(request);
    return CreatedAtAction(nameof(Get), new { id = character.Id }, character);
}

One thing to note here is the inclusiong of the class CreateCharacterRequest. This is what is referred to as a Data Transfer Object and as the name suggests they are for the transfer of data to a format applicable to the database entries. As it stands, we only need two values when creating a character, the name and the level. It is likely in the future that we will need a bit more information for when a new character is created (such as class or race) and so rather than having the method take in a long list of values that it can then use to create a character in the database, we can create a DTO to make things easier. These will be saved in a folder called DTOs.

public class CreateCharacterRequest
{
    public string Name { get; set; } = string.Empty;
    public int Level { get; set; }
}

This is similar to a model, however we don't need to include any data annotations from Entity Framework as this won't be used for adding tables to the database and won't be connected directly to the database context. All it needs is to account for all the different columns we'd need data for when creating a new character. We don't need to include an ID value as that will be created automatically once it's input into the database.

Then we have to have the method that actually creates and saves the new character in the database. Again, we need to get the database context and then map the request item onto the model for the database before saving it into the database. This class is saved in a Creators folder. While it's fine for now since the character model is quite small, it may be worth using Mapper classes or even Maps in the future. We don't need to input an ID for the new character as it's the key for the table and will automatically add a new ID for it.

private readonly ToffeeContext _context;

public CreateNewCharacter(ToffeeContext context)
{
    _context = context;
}

public async Task<Character> Create(CreateCharacterRequest request)
{
    var newCharacter = new Character
    {
        Name = request.Name,
        Level = request.Level
    };
    _context.Characters.Add(newCharacter);
    await _context.SaveChangesAsync();
    return newCharacter;
}

The next method, to update a character, is very similar.

[HttpPut]
public async Task<IActionResult> Put([FromBody] UpdateCharacterRequest request)
{
    var updatedCharacter = await _updateCharacterById.Update(request);
    return Ok(updatedCharacter);
}

This uses the HttpPut method which is for when data is being changed. Again, this uses a DTO called UpdateCharacterRequest which is also similar to the CreateCharacterRequest DTO but includes the ID since we'll need that to find the character.

public class UpdateCharacterRequest
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public int Level { get; set; }
}

The UpdateCharacterById class can then be made to find a character in the database and then update it based on the given info.

public class UpdateCharacterById : IUpdateCharacterById
{
    private readonly ToffeeContext _context;

    public UpdateCharacterById(ToffeeContext context)
    {
        _context = context;
    }

    public async Task<Character> Update(UpdateCharacterRequest request)
    {
        var character = await _context.Characters.FindAsync(request.Id);
        if (character == null)
        {
            throw new Exception("Character not found");
        }
        character.Name = request.Name;
        character.Level = request.Level;
        await _context.SaveChangesAsync();
        return character;
    }
}

This class features error handling in case there is no character with the given ID in the database, and then updates all the values of the character. One issue with this at the moment is that it will change all the data of the character, so if a user just wants to change the level, they will need to input the character's name again to ensure it doesn't get changed.

I've now uploaded this API to a public GitHub repository so feel free to take a look at it.