Introduction to the Web API Project
In this video, we will learn how to create a web API using ASP.NET Core and C#. By the end of the video, you will be able to create an API that supports creating, reading, updating, and deleting data. My name is Pat. If you are new to the channel, consider subscribing. Let's get into it.
API Overview and Initial Project Setup
The API we are going to create will be able to manage books. We will expose various endpoints for creating, reading, updating, and deleting books. This endpoint will be used in combination with an HTTP verb to perform a specific action on the data.
In case you want to follow along, I will use Visual Studio 2019 and the .NET 5 SDK. Let's launch Visual Studio and then select create a new project. On the next screen, select the ASP.NET Core Web Application, and then Next. In this dialog, we'll give a name to the project. Let's name it BookApi and then click Create. In this dialog, select ASP.NET Core Web API, then click on Create to generate all the project files.
Cleaning the Project and Installing Dependencies
Well done, the project is created. Let's remove two files we don't need: WeatherForecast at the root of the project and WeatherForecastController in the controllers folder.
Behind the REST API, we will use Entity Framework to interact with a SQLite database. Entity Framework is an object-relational mapper. It allows working with a database using .NET objects instead of SQL statements. Let's start by installing a NuGet package named microsoft.entityframeworkcore.sqlite. This package has everything that helps work with Entity Framework and SQLite. Using Entity Framework Core, data access is performed using a model.
Creating the Data Model and DbContext
We need to create a book model. Let's create a folder named Models, then add a new class named Book. This class has a few properties: Id, which is a unique identifier; Title represents the title of the book; Author is the name of, well, the author; Description is a short description of the book. In the real world, a book has probably more attributes than this.
Entity Framework Core also needs a context object. The context object allows querying and saving data. In the Models folder, let's add a new class called BookContext. This class inherits from the DbContext class. The constructor takes a DbContextOptions object as a parameter. This object is a configuration of the context. It will be injected through dependency injection. Let's also expose a DbSet property that represents a collection of books. Let's make sure the database is created when using the DbContext.
Configuring Dependency Injection for the DbContext
Once the BookContext is created, we need to register it for dependency injection. Let's update the ConfigureServices method in the Startup.cs file. We use the AddDbContext to register the BookContext class with ASP.NET Core's dependency injection system. We also provide a connection string to the SQLite database, which is a simple file in our project. If you have an existing database, this is the place where you can set your connection string.
Introducing the Repository Pattern
Entity Framework is all set. It's time to add a repository. Simply put, a repository is a layer that sits between our application and the data access layer, which is in our case the BookContext. Adding a repository is a good practice; it helps add a layer of abstraction between your code and the data access layer. Let's create a folder named Repositories, then we add an interface called IBookRepository.
This interface describes operations that can be performed against the database. Get will retrieve all books. Get with an ID parameter will retrieve a single book. Create, update, and delete are self-explanatory.
Implementing the BookRepository
Let's add a concrete implementation. In the Repositories folder, we add a new class called BookRepository. This class implements the IBookRepository interface. The repository will query the database using the BookContext. We inject the context through the constructor.
Let's implement the Create method. We use the Add method of the DbSet to add a new instance of the Book class. The SaveChangesAsync method will insert data into the database. In the Delete method, we use the Remove method of the DbSet, and the SaveChangesAsync will delete the entity from the database.
Let's implement the Get method now. Invoking ToListAsync on the DbSet will fetch all the books from the database. The other Get method takes an ID parameter; we will use the FindAsync method to get a single book. The last method is the Update method. We change the state of the entity, and SaveChangesAsync will update the entity in the database.
Registering the Repository for Dependency Injection
The repository is ready. Now we need to register it with the dependency injection system. Let's update the ConfigureServices method in the Startup.cs file. AddScoped will register an instance of the BookRepository. This also means that only one instance of the BookRepository class will be created for a given HTTP request.
Creating the API Controller
The repository is ready. Let's create an API controller. An API controller is a class that is responsible for handling requests for an endpoint. Right-click on the Controllers folder, next Add Controller, then select API Controller - Empty. We name the class BooksController.
Let's take a look at the structure of the controller. It has a Route attribute that defines the path that the controller will handle. In our case, the path will be api/books. The ApiController attribute provides behavior such as automatic model validation and more. The controller inherits from ControllerBase, which provides many properties and methods that are useful for handling HTTP requests. The controller needs an instance of the BookRepository to interact with the database. Let's inject the BookRepository in the constructor. Now let's implement methods that are going to handle specific HTTP requests. These methods are called action methods. We will create one for each HTTP verb we want to handle.
Implementing the GET Endpoints
Let's create a new method called GetBooks. This method returns an IEnumerable<Book>. We decorate the method with the HttpGet attribute. This tells ASP.NET that the method will handle HTTP GET requests. We get the books from the repository using the Get method. When this action is invoked, ASP.NET will convert the book's object to JSON before returning it to the caller.
Let's create a new method also named GetBooks. This method returns a Task<ActionResult<Book>>. Why can't we just return a book object? The Task part is because the caller will be able to await this method. The ActionResult provides the flexibility to return other types like NotFound or BadRequest, for instance. The method takes an ID as a parameter. It's also decorated with HttpGet to tell that it will handle HTTP GET requests. Notice that the HttpGet in the attribute is used with an argument in curly braces. This puts the endpoint's subpath in the id parameter. If we use api/books/3, for instance, then the id parameter will have 3 as a value. We get a single book from the repository using the Get method that takes an id.
Testing the GET Endpoints with Swagger UI
Let's run the project to test what we've done so far. As you can see, the default page comes with documentation of the API. The template we use to create the project comes with OpenAPI support. This gives us Swagger UI, which is a tool that generates documentation for our API. It also supports testing the API in the browser.
Let's try the GET endpoint. If I execute it, of course, there is no data because our database is empty. Let's add a breakpoint inside the GetBooks actions. If I execute the GET endpoint again, as you can see, the breakpoint in the first GetBooks method is hit. Let's do the same with the second Get. I provide a random ID, and if I execute, the breakpoint is also hit. The endpoint seems to work. Let's implement an action method for posting a book.
Implementing the POST Endpoint
In the controller, we add a new method called PostBooks. The method takes a Book object in parameter and returns a Task<ActionResult<Book>>. We decorate the method with the HttpPost attribute to tell that it will handle HTTP POST requests. Thanks to a process called model binding, ASP.NET will convert a JSON in the request payload to a Book object. We use the Create method on the repository to insert a book in the database. We return a CreatedAtActionResult, which will generate a 201 HTTP status code.
Testing POST and Verifying with GET
Let's run the project to test this endpoint. Select POST. In the request body, we add a JSON text. This JSON represents a book. We set some properties. We execute. If we check the result, we get a 201 HTTP status code.
Let's add another book and execute. Now, let's test the GET endpoint. If I execute, I get the two books I posted.
Implementing the PUT Endpoint
Now let's add an action method to update an existing book. We add a new method called PutBooks. It returns a Task<ActionResult>. It takes an ID parameter, which is the ID of the book we want to update. It also takes a Book parameter, which is the updated book. It is decorated with HttpPut to tell that it will handle HTTP PUT requests.
We make sure that the ID provided in the URL and the one in the payload is the same, otherwise we return a BadRequest result. BadRequest generates a 400 HTTP status code, which indicates that the server cannot understand the request. If the ID is okay, we invoke the Update method of the repository. We return a NoContent result that will generate a 204 HTTP status code to indicate that the request has been processed, but there is no data.
Testing the PUT Endpoint
Let's test this endpoint. Let's first execute a GET so we can copy a book payload. Now in the PUT endpoint, I set the ID to 2, paste the payload, and I modify the description. If I execute the request... if I execute the GET endpoint again, you can see that the description has been changed.
Implementing the DELETE Endpoint
Let's implement the action method for deleting a book. We add a new method called DeleteBooks. The method takes an ID parameter, which is the ID of the book we want to delete. It returns a Task<ActionResult>. We decorate it with an HttpDelete attribute to tell that it will handle HTTP DELETE requests. We first check if the book exists in the database. If the book exists, we invoke the Remove method from the repository to delete it. Finally, we return a NoContent result.
Testing DELETE and Final Conclusion
Let's test the endpoint. I provide the ID 2, which is the ID of the second book, and I hit execute. If I execute the GET endpoint, as you can see, the second book is no longer there.
That's it for this tutorial. If you enjoyed it, feel free to like the video and subscribe to the channel. Thanks for watching. See you soon.