Organizing projects in ASP.NET Core is essential for maintaining clean and manageable code. Architectural patterns can help. Check out this blog post on implementing one of the most well-known architectural patterns, the onion pattern.
Architecture patterns are design approaches that help organize and structure web applications for optimal maintainability, scalability and flexibility.
This post will explore one of the most widely adopted standards today: the onion architecture. Additionally, we will discuss the practical application of this pattern in an ASP.NET Core application.
Architecture patterns are high-level design solutions or models that provide a structured approach to organizing and designing the architecture of software systems.
These patterns offer a set of best practices and guidelines for solving common design problems that developers encounter while developing complex applications. Architectural patterns help software systems to be scalable, easy to maintain and adaptable to changing requirements.
Key features of architectural patterns include:
Reusability: Architectural patterns are reusable solutions that can be applied to different projects and domains. They encapsulate design expertise, making it easier for developers to apply proven design concepts.
Abstraction: Patterns provide a level of abstraction that focuses on the high-level structure and organization of a system rather than specific implementation details. This abstraction allows developers to think about system architecture in a more conceptual and general way.
Scalability: Architectural patterns are designed to accommodate future growth and changing requirements. They help ensure that a system can scale in functionality and performance.
Maintenance: By promoting a clear separation of concerns and modularization of components, architectural patterns facilitate the maintenance and extension of a software system over time.
Consistency: Patterns establish a consistent structure and design approach, which can be beneficial in team environments and large software projects.
Documentation: Standards come with a large amount of documentation and resources, which helps developers understand and apply them effectively.
In the context of ASP.NET Core, there are some widely used patterns, including:
In this post, we will learn about one of these patterns—the onion architecture pattern.
The onion architecture pattern is a software architecture pattern widely used in ASP.NET Core and other modern application development frameworks. It is a variation on traditional layered architecture that promotes a more flexible and sustainable way of designing and structuring applications. Jeffrey Palermo popularized the onion architecture pattern, which is particularly suitable and recommended for building robust, maintainable and testable applications.
The main idea of onion architecture is to organize the application into circles or concentric layers, with each layer depending only on the inner layers.
Next, let’s learn about and implement each of the four main layers of a typical onion architecture application in ASP.NET Core.
To create the sample project you need to have the following:
You can access the complete source code here: Source code.
At the end of the post, the complete project will have the following structure:
First, let’s create an ASP.NET Core solution project, where we will store the application layers. So, in the terminal run the following command:
dotnet new sln -n BookingFast
This is the most internal layer and contains the most critical part of the business logic, representing the core of the application, and must be completely independent of any external structures. In the core layer, you define your domain models, business rules and application-specific logic. This layer should have no dependencies on the other layers and is often called the “Domain” or “Entities” layer.
To create the domain layer in the project and add it to the solution class, use the following commands:
dotnet new classlib -n BookingFast.Domain
dotnet sln BookingFast.sln add BookingFast.Domain/BookingFast.Domain.csproj
Now inside the “BookingFast.Domain” folder, create a new folder called “Entities” and inside it create the following class:
The “Reservation” class is the main entity of our application, so it belongs to the domain layer, which is the innermost layer in an onion structure.
Next, let’s create the Infrastructure layer.
The infrastructure layer is responsible for interacting with external systems, structures and services. In the context of ASP.NET Core, this layer includes code related to data access, communication with external services and other infrastructure issues. This layer can have dependencies on external libraries, frameworks and ASP.NET Core itself.
To create the Infrastructure layer and add it to the solution, at the root of the project execute the following commands:
dotnet new classlib -n BookingFast.Infrastructure
dotnet sln BookingFast.sln add BookingFast.Infrastructure/BookingFast.Infrastructure.csproj
First, we need to download the dependencies to the infrastructure layer, so open a terminal in the infrastructure project and execute the following commands:
dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions --version 8.0.0
dotnet add package MongoDB.Driver --version 2.22.0
Now, let’s create the class that will contain the variables responsible for storing the values of the database connection string.
Then, inside the “BookingFast.Infrastructure” folder, create a new folder called “Repositories.” Inside it, create a new class called “StudentDatabaseSettings” and place the following code in it:
In this example, we will create a database in MongoDB Atlas, which is a very simple database. To do this, you first need to create a server in MongoDB Atlas and create the database. If you’re new to MongoDB Atlas, I recommend this guide for creating and configuring your first cluster: MongoDB Atlas Getting Started (Atlas UI tab).
With the cluster configured, we can create a “reservation_db” database and a “students” collection as in the image below:
To connect our application to the cluster and access the created database, we need to obtain the connection string, which we will use later. To get it, just follow the steps shown in the images below:
In your database, click “Connect” > “Drivers” and in the window copy the connection string, as shown in the image below.
Now that we have the connection to the cluster, let’s implement the configuration in the project. Replace the code in the “appsettings.json” file of the “BookingFast.UI” layer with the code below:
In the code above, replace "<your cluster connection>"
with your previously obtained cluster connection. Also, remember to replace "<password>"
and "<username>"
with the cluster password and username.
Now let’s create the repository interface with the methods responsible for database operations.
In an onion architecture, a repository interface is usually found at the domain layer, as repositories are part of the data access logic and are a fundamental part of the application domain.
So, inside the “BookingFast.Domain” folder, create a new folder called “Infra.” Inside that, create another folder with the name “Interfaces” and add the following interface inside it:
To create the Repository class, we will use the infrastructure layer, so inside the “BookingFast.Infrastructure” folder, inside the “Repositories” folder create the class below:
ReservationsRepository
Note that we use the “IReservationsRepository” interface, which is from the domain layer. To use it, we need to add the reference to the domain layer in the infrastructure layer. So, double-click on the file “BookingFast.Infrastructure.csproj” and add the code below to it:
The Infrastructure layer is ready, the next step is to implement the Application layer where we will create the service class.
The next concentric circle is the application layer, which depends on the domain layer but should also not have dependencies on external frameworks. This layer contains application-specific services, use cases and application logic. It acts as an intermediary between the omain layer and external layers such as the UI and infrastructure layers.
To create the application layer and add it to the solution, execute the following commands in the terminal, in the project root:
dotnet new classlib -n BookingFast.Application
dotnet sln BookingFast.sln add BookingFast.Application/BookingFast.Application.csproj
First, let’s create the Data Transfer Object classes (DTOs) that will be the classes exposed to the UI layer and that represent the entity model. In this case, it will be the “ReservationDto” class.
So, inside the “BookingFast.Application” folder, create a new folder called “Dtos” and inside it create a new class called “ReservationDto.” Place the code below in it:
Here we also need to add the dependencies of other layers, which are the domain and infrastructure, so double-click on the “BookingFast.Application” file and add the code below:
The next step is to create the service class and methods to perform bank operations through the infrastructure layer. Inside the “BookingFast.Application” folder, create a new folder called “Services” and inside it create the following interface and class:
The outermost circle is the UI layer, which includes the application’s user interface components. In the context of ASP.NET Core, this layer includes controllers, views and other components responsible for handling HTTP requests, user input and UI rendering. The UI layer depends on the application and infrastructure layers, but should not contain any business logic. It mainly handles user interactions and invokes application services.
To create the UI project, run the command below:
dotnet new web -n BookingFast.UI
This command will create a new project using the ASP.NET Core Minimal APIs template. Next, run the following commands to add the “BookingFast.UI” project to the solution class:
dotnet sln BookingFast.sln add BookingFast.UI/BookingFast.UI.csproj
Now let’s add the reference to the application layer. Double-click in the “BookingFast.UI.csproj” file and add the following code snippet:
Then, let’s download the NuGet packages to the UI layer. Open a terminal in the UI project and execute the following commands:
dotnet add package Microsoft.AspNetCore.OpenApi --version 8.0.0
dotnet add package Swashbuckle.AspNetCore --version 6.5.0
The next step is to create the controller, which will call the service class methods and expose the data through the endpoints.
In the UI layer, create a new folder called “Controllers.” Inside it, create a new file called “ReservationsController.cs” and place the code below in it:
The last step is to configure the dependency injection of the classes. In the “Program.cs,” file replace the existing code with the code below:
To test the application, open a terminal in the UI project and execute the following command:
dotnet run
In the browser, access the address http://localhost:5202/swagger/index.html
and you can execute the operations in the Swagger interface, as shown in the GIF below:
In summary, the onion architectural pattern stands out as a notable approach to structuring and sustaining ASP.NET Core projects efficiently. Throughout this post, we explored the basics of this pattern and examined its practical application.
Whenever you create a new project, consider using the onion pattern. This way, you will not only take advantage of the structural advantages offered by the pattern, but you will also be investing in more readable, sustainable and easily maintained code.