Decomposing My .Net API (Part I)

“I know what I have done, but I don’t know why I should do it!”
If you ever have this feeling while trying to build a webapi using ASP.NET Core, read along and you may find your answer.
Disclaimer: this is not a course on building a webapi, I only aim to answer questions like “Why should I use this in my code?” or “What is it inside my code?”
What’s in the boilerplate?
Make sure you are using .NET Core version 3, otherwise the convention may be different.
ASP.NET Core can generate over two dozens of templates, the one that I’m using is webapi
.
$ dotnet new webapi -n my-api
.NET will auto-generate a template, and in our target directory, first off, obj is a folder you don’t need to worry about for this walk-through.

Find yourself an editor, and open this folder with it.

Out of the bag, there are several files, now let’s explain them one by one:
Program.cs
This is the main
file, it’s official name is a Generic Host. Specifically for the webapi, the Main(string[] args)
calls the function CreateHostBuilder(string[] args)
to build the application main thread(Host).

CreateHostBuilder(string[] args)
may look weird because it uses a lambda expression which omits a few default settings, here is what it does, in full:
There are several configurations in this function:
- Line 9, 10 references to our
appsettings.json
andappsettings.Development.json
files. - Line 13 add environmental variables, for example, we can
set
the environment intoProduction
orDevelopment
in the command line. - Line 15–18 specifies that we can use the command line argument to pass in some settings.
- Line 20–24 uses a web-specific builder to introduce
ChainedConfigurationProvider
which is theIConfiguration Configuration
inStartup.cs
Startup.cs
This class contains a IConfiguration Configuration
property. The constructor will pass in the default configuration
in Program.cs
while it calls webBuilder.UseStartup<Startup>()
.
Additionally, you can add configurations inside this class based on your own project development.
ConfigureServices
allows you to add services (reusable components) to your configuration, so you can use those functions across your application.
Configure
gives you the ability to add components to your application pipeline. By pipeline, it means data will be processed at each component then pass on to the next one, hence the order is necessary.
appsettings.json
JSON files in the directory are used to store key-value pairs, which contains configuration strings (for example, secret code to encrypt JWT). This file can be configured to cater to different environments.
*.csproj
This is a project file, it will be used by the Microsoft Build Engine to build your project. This file contains information needed for the MSBE, NuGet packet manager will put packet references inside this file.
launchSettings.json
This is a development environment only file. In this file, you can set the webserver you want to use, localhost connection strings, and other profiles to be used in a development setting.
WeatherForecast related files
They are sample files for the webapi. WeatherForecast.cs
is called a model and WeatherForecastController.cs
is called a controller. For this webapi, only models and controllers will be implemented, the view (data presentation) will be handled by a client application(be it a react/angular single page application).
Design for this API
Now into this API, it handles two parts of a broader sense of MVC architecture, the models and the controllers. In this implementation, these two parts are further split into four different concepts: model, repository, controller, data transmission object.

- The database is where you store the data.
- To use the database, you will need to map your models/entities to the database using a Data Context. (User model to the User table in DB)
- At this stage, you can use data context to persist data into the database, however, code will be repetitive and complicate for certain operations. Repository pattern is therefore used to encapsulate operations on persistent data.
- Models are the data objects you designed for your application.
- Data Transfer Objects (DTOs) define how the data will be sent over the network.
- Controllers decide what to send back to users when they make requests.
Knowing what the design is, I’ll first show you the core folders and files for the design in this part. Then in the next part, I’ll stitch them together with various functionalities.
Models folder
Models are essentially responsible for forming your data, it defines what are the attributes relevant to the conceptual object you are modeling.
CollectionGundam.cs
This model is an entity, which has a physical meaning to be specific. This means it has to have a unique key to make it distinguishable.
That’s where the public int Id { get; set; }
comes into play. With the help of the Entity-Framework, this Id
will be converted into a sequential id inside the database with it will be automatically incremented.
The rest of the data fields are the attributes needed to describe a Gundam Model object, different attributes can be declared and defined for your specific model.
Like.cs
This is a data entity, but unlike the CollectionGundam
, it represents a virtual relationship. A CollectionGundam
can be liked by many User
, meanwhile, a User
can like many CollectionGundam
. So in this many-to-many relationship, Like
works as a lookup table. It doesn’t need a unique identifier, but rather, it can be uniquely identified by its Liker
and Likee
. This is why this Like
doesn’t have an Id
.
Data folder
The data folder contains repositories for accessing the database. They are a layer of abstraction between the web application and the database. They describe how data will be retrieved from and put into the database.
DataContext.cs
This is the main component to control the interaction between the application and the database. DbSet<Entity>
corresponding to different tables(entities) in your database.
There is a line of code that might be confusing:
When you set up this DbContext
, a database should be provided to the constructor, or as per official documentation, a DbContextOptions
will be provided and configured externally.
The configuration is done in the Startup.cs
where the program is informed to use SQLite
and read the connection string from appsettings.json
.
To set up a table inside the database, DbSet<TEntity>
is instantiated with the model of your choice as the TEntity
and the name
of the table as the property name
.
CollectorRepository.cs and ICollectorRepository.cs
Repositories are in charge of operations on corresponding tables in a data persistence layer. By declaring the operations inside an interface, we can implement data persistence using different technologies (be it a database, or XML).
We first instantiate the DataContext as a readonly
attribute for the repository, it will be the handler for our query to the database.
For actions that do not retrieve data from the database, there’s no need for an async method. On the other hand, for those who do, you need to make them async so that they can wait for the data to be sent back.
Specifically in this case, when a method is to retrieve the data for a specific user, it should be a Task which on completion, returns an object of User class. This is called the Task-based Asynchronous Pattern.
Task<User>
is aTask<TResult>
class, aka a task with a return value of the typeTResult
. It executes on a separate thread other than the program main thread, and upon completion, returns an object ofTResult
.
In this method, this.context.Users
locates the Users
table inside the database, then .FirstOrDefaultAsync(u => u.Id == id)
will find the first user
whose Id
property has the same value as the id
parameter we passed in. .Include(p => p.photos)
joins the Users
table and the UserPhotos
table then includes photos that belong to this specific user. Of course, we have to await
for this operation to fully transfer the data. Then we store the data inside a user
variable and return it as an User
object.
DTOs folder
Data Transfer Object is in charge of moving data between different layers. It takes whatever data(properties) you have, and map into the class you specified.
There are various reasons for this design pattern. In this project, we want to:
- Remove circular reference (User refer to Photo which then refers to User)
- Hide particular properties that clients are not supposed to view (User has password data which should not be sent back to other users)
DTOs are easy to set up as well, you only need to have the properties to be transferred (with getter and setter) inside that particular DTO.
For example, when users are logging in, they usually type in their username and password. Then you will need a DTO which contains only username and password as properties.
However, you will need a mapper to map a DTO object from (or to) a model object.
Controllers Folder
Controllers handle client requests and send back data correspondingly. As per MS Documentation, whenever you type in an URL inside your browser, it corresponds to a controller action.
To further extend this idea, your interaction with the app which sends back request to the server will trigger controller actions. Be it a GET request to load a page, a POST request to submit a form, a DELETE request to remove a photo or comment.
UsersController.cs
This controller handles requests that access the endpoint */api/users
.
For every controller, you need to specify [Route("api/[controller]")]
,[ApiController]
, and public class {Name}Controller : ControllerBase
. They indicate the current class is an API controller with the route of api/{Name}(lowercase)
.
The ICollectorRepository
and ICollectionGundamRepository
are two of the major data resource for this particular controller, therefore they need to be injected. IMapper
maps a DTO
object to a model object, and we will further discuss the implementation logic.
As for a specific controller action, you will need an endpoint with an accepted access method assigned to it. [HttpGet("{id}")
means that only GET
method can trigger this action on endpoint api/users/{id}
. It’s an async
function, because upon completing the Task
, this action can then return the expected object as an HTTP response.
So far, we’ve discussed the essential parts of this webapi. In the next part, I’ll stitch them together with functionalities, and further explain how things are working in Startup.cs
and what are those helper classes about?