In the previous chapter, we explained the range of approaches including CRUD Operations, creating entities model classes, implementing controllers for CRUD Operations, RESTful routing and the HTTP protocol, validating, error handling, and finally optimizing database performance. This chapter deals with CRUD operations more now and the concrete implementation of the model class, the controllers of CRUD operations, serialization of HTTP requests and responses, and the Route Attributes are discussed.

I) CRUD Operations

CRUD is a reference for activities such as Creating, Reading, Updating, and Deleting (CRUD). They are the actions that you can always execute without any restrictions no matter which system is managing data.

II) Defining Model Classes

The code snippet for the Book model class was provided previously:

public class Book
  public int Id { get; set; }
  public string Title { get; set; }
  public string Author { get; set; }
  // Other properties

This class shall be a basic one and it will have properties like Id, Title, and Author which are generally found in books. You may add properties as you please and build as much as you like.

III) Implementing Controllers for CRUD Operations

Here's the BooksController class with code for CRUD operations:

public class BooksController : ControllerBase
  private readonly IBookRepository _bookRepository;

  public BooksController(IBookRepository bookRepository)
    _bookRepository = bookRepository;

  // GET: api/books (Read all)

  // GET: api/books/5 (Read by ID)

  // POST: api/books (Create)

  // PUT: api/books/5 (Update)

  // DELETE: api/books/5 (Delete)

This interface is used for insertion/deletion/updating of book records in the database.

See the way the 'route' attribute is utilized by methods like [HttpGet], and [HttpPost] to depict what each action is for.

The _bookRepository is injected using dependency injection (which will be explained later) and serves as a conduit for accessing methods that communicate with the storage data layer, usually the database.

Each method follows a similar pattern:

  • The repository is also an interface that retrieve data (Read), add data (Create), update data (Update), or delete (Delete) data using the _bookRepository methods.
  • It can return appropriate HTTP status codes and response-based data according if the operation has succeeded or failed.
1) GET: api/books (Read all)
public async Task<ActionResult<IEnumerable<Book>>> GetBooks()
  var books = await _bookRepository.GetAll();
  return Ok(books);
  • Method:GET
  • Functionality: Retrieves a list of all book objects in the system. 
  • URL Structure: api/books: The base URL for accessing book data.


  1. The application uses a GET method to api/books.
  2. The server pinpoints out the BooksController and through that activation, the GetBooks method decorated with [HttpGet] is triggered.
  3. This method is employed for reading from the data storage (of course, using _bookRepository).
  4. The server sends back the response with the list of Book objects, each being the representation of a book in the system.
  5. This application is capable of receiving and processing an array (e.g., as a table or list) of books that it then presents in such a way.
2) GET: api/books/5 (Read by ID)
public async Task<ActionResult<Book>> GetBook(int id)
  var book = await _bookRepository.GetById(id);
  if (book == null)
    return NotFound();
  return Ok(book);
  • Method: GET 
  • Functionality: Retrieves a specific book based on its unique identifier. 
  • URL Structure: api/books/{id}: The base URL with a placeholder {id} for the book ID.


  1. The application of your request will usually consist of a GET request from the URL with a particular book Id that will be such as api/books/123 (for example with the book number 123).
  2. The server takes the request and locates the BooksController and then initiates the function GetBook(int id) marked as [HttpGet("{id}")]. The URL role will be replaced with the id parameter, which is the same as the id parameter in the method.
  3. This method uses the repository book data containing the specified Id, which owns the form of the data storage.
  4. The server replies with a response. A successful endpoint extracts the details of one particular copy of the book, represented by its unique ID. If we are unable to find any book with this ISBN, we simply issue an error response (e.g., "Not Found").
  5. Your application now sets out to retrieve more data so that you can get the details of the book if everything goes well and wall this information with the specific book.
3) POST: api/books (Create)
public async Task<ActionResult<Book>> CreateBook(Book book)
  await _bookRepository.Add(book);
  return CreatedAtAction(nameof(GetBook), new { id = book.Id }, book);
  • Method: POST 
  • Functionality: Creates a new book record in the system. 
  • URL Structure: api/books: The base URL for accessing book data.


  1. The application sends a POST request to api/books which are submitted with a new book object in the request body (it normally contains data related to the book title, author, etc.).
  2. The server recognizes the BooksController and consequently calls the CreateBook method utilizing the HttpPost enrichment.
  3. This approach is through the _bookRepository which allows syncing the available book data into the storage by following the right steps.
  4. The process is completed with the server responding with the “Created (201)” status code along with the generated book object in its Id.
  5. In this case, your application was confirmed and could now show additional data, such as the details of the book that has just been added.
4) PUT: api/books/5 (Update)
public async Task<IActionResult> UpdateBook(int id, Book book)
  if (id != book.Id)
    return BadRequest();
  await _bookRepository.Update(book);
  return NoContent();
  • Method: PUT 
  • Functionality: Updates an existing book record in the system. 
  • URL Structure: api/books/{id}: The base URL with a placeholder {id} for the book ID.


  1. The data containing updated book information is sent via PUT request to the api/books/123 URL address and it is accompanied by the request body.
  2. The server gets hold of the BooksController and the UpdateBook(int id, Book book) method gets a call (decorated with [HttpPut("{id}")]).
  3. This approach validates whether the inputted ID in the URL matches the ID in the book object. In case it is incorrect, an error message is provided.
  4. On IDs match, it uses the _bookRepository to update the data already saved in the storage with the information entered.
  5. After successful modification, the server will reply with a corresponding HTTP status code (e.g. "No Content").
  6. Once the transaction is approved, the application can be updated with book details.
5) DELETE: api/books/5 (Delete)
public async Task<IActionResult> DeleteBook(int id)
  var book = await _bookRepository.GetById(id);
  if (book == null)
    return NotFound();
  await _bookRepository.Delete(book);
  return NoContent();
  • Method: DELETE
  • Functionality: Deletes a book record.
  • URL Structure: api/books/{id}: The base URL for book data with a placeholder {id} representing the book ID to be deleted.


  1. The request of your application is to send a DELETE command to the URL being the preceding one including the particular ID for the desired book. For example, (assuming you want to delete the book of ID 123).
  2. The server processes the request and then directs the request to the BooksController class, which is known as managing this endpoint.
  3. The DeleteBook(int id) method that is placed within the controller gets executed with the help of [HttpDelete("{id}")]. The url path in the {id} is interpreted to the parameter id within the method definition.
  4. This method is probably going to run the _bookRepository and make a get request for the book data with the respective id and then store it using the data storage.
  5. The controller performs a process. If the book with a specified id cannot be found, it usually results in an error response by giving (for example "Not Found").
  6. By enabling the book and the DeleteBook methods the _bookRepository will permanently delete the record of the book from the data storage.
  7. When a record is successfully deleted, the server returns a response with an applicable HTTP status code. This means it'll be an HTTP status code "204 No Content" showing a successful delete operation with no content (response body) in the body.
  8. The next step is that your request gets back to the application for it to understand the given status code. It would probably redo its user interface for the book's removal (for example, showing it in a list of displays).

IV) Handling HTTP Requests and Responses

The controller is the class responsible for handling the requests and responses for printing in HTTP. For example, in the CreateBook method:

  • It takes the Book instance from the request body as shown in the example of POST request with book data.
  • It implements a method named `addBook` which takes `bookRepository` as a parameter and adds the book to data storage using it.
  • Once it has successfully been "Created", the API returns a "Created (201)" status as well as the book object in the response body.

V) Using Route Attributes

The code snippet demonstrates route attributes:

  • Sub-attribute determines the paths to the actions located in this controller. Such an interface will provide the URL api/books for all the book-related operations.
  • Moreover, individual approaches like GetBooks (using HttpGet ) provide further evidence that this method is responsible for responding to GET requests utilizing the URL /api/books.

This chapter is now complete, and in the next part, we'll delve into Dapper ORM with stored procedures. Dapper, developed by Stack Overflow, is a micro-ORM that emphasizes simplicity and performance, rtc. in contrast to EF Core.