Controller, service, and repository layers using ASP.NET Core
Before learning .NET, I had a lot more experience with Ruby on Rails. One of the ideas I found in .NET development that I hadn’t encountered in Ruby on Rails is this design of a web application with a service layer between the controller and repository, and so I want to illustrate the design.
In this post, I will start with a Ruby on Rails controller with one simple action, translate it to ASP.NET Core, and then refactor that into the design that I want to illustrate.
The starting point: an action to get a book
Here is an example of a very simple Rails controller with one action:
If I translate this to ASP.NET Core, it’s roughly something like this (there are some differences, but they are not important for the comparison I’m making):
Change the return type from using an entity type to a data transfer object
Currently the return type of the action is ActionResult<Book>. Book is an entity, which means it is a type that is mapped to the database in the object relational mapping (ORM), so the JSON structure of the API is coupled to the database. We can use a Data Transfer Object instead:
For completeness, the Book entity might look like this:
With the data transfer object, the example is now:
Now the API surface is not coupled to the entity type and can be designed independently to fit the needs of the application.
There are different approaches to mapping between types, and the above shows a straightforward way. Here is another:
Another reason to use data transfer objects: prevent overposting attacks
Consider this example:
With ASP.NET Core model binding, the incoming request data is used to instantiate the argument for the action’s book parameter. This is then used to update the corresponding record and could allow for an overposting attack, as any property of Book can be assigned through model binding, and then updated in the database. If a data transfer object is used to bind the incoming data instead, this sort of vulnerability is ruled out.
In Rails, the Strong Parameters feature protects against this sort of vulnerability.
Using a repository to do database access instead of the DbContext
The next refactor is to implement a Repository pattern for database access. At the very least this provides an interface for mocking the database in unit tests. I’ve also found it useful for implementing a deep interface for supporting sorting, searching kind of features as an abstract base repository, since those operations are most efficiently carried out by the database, and also for encapsulating what the standard eager loading behavior is for each entity.
That will need to be registered in Program.cs for dependency injection:
Now the controller can do database access using the repository instead:
However in this design we do not want the controller to do database access even through the repository.
Adding a service layer
The last change is to add a service layer between the controller and repository.
Since there is no real business logic for this example, this is kind of just a pass-through method, but this layer is for business logic and can be unit tested by mocking the repository.
Putting it all together
So here is the simple Rails controller example that I started with:
Here is the same implemented in ASP.NET Core with the data transfer object, service, and repository layers design: