In this article, we will talk about implementing Specification Pattern in ASP.NET Core applications and how it can enhance the already existing Generic Repository Patterns. We will be building from scratch, an ASP.NET Core WebAPI with Generic Repository Pattern, Entity Framework Core and finally implement the Specification Design Pattern. You can find the complete source code of this implementation here. Letās get started.
Understanding the Specification Pattern: The Why?
Letās walk through a simple example to understand the need to use Specification Pattern. Below is a class snippet of Developer with the required properties like Name, Email, Experience, and so on.
Now, we would be probably having a service layer that returns dataset from the DB over an abstraction like Entity Framework Core. Here is how it would look like.
Although you would be getting the count of all developers, a more practical and logical requirement would be to get the count of developers with some kind of filters, agree? For instance, get the count of developers whose estimated income is $100,000 or more, or the developers with 5 years of experience or more. The possibilities are quite limitless.
However, this would end in you having tons of service layer functions like GetDeveloperCountWithSalariesGreaterThan(decimal minSalary), GetDeveloperCountWithExperienceMoreThan(int minExp), and much more. The more requirement comes in, the more is the count of functions you would end up having. And what if you need the count of developers with salaries greater than x and experience higher than y years? Thatās yet another challenge that may result in extra methods.
You may argue that you can directly apply these filters to the Entity Framework Core Entity, something like
But, NO. This is nowhere near a clean application codebase you would need. This approach will end up messing up the scalability of your application quite soon and trust me, this is not at all maintainable. Quick Tip, You always need a service layer within your application that sits between your application and Database and is solely responsible to handle the Business Logics.
This is where your application needs to use Specification pattern. Heads up, there are a few limitations to the Generic Repository Pattern which are resolved with the usage of Specification Pattern. We will build up a project and come to a point where you would practically need to use Specification.
What we will Build.
For demonstration of Specification Pattern in ASP.NET Core, we will build a simple WebAPI Application that has 2 endpoints:
- Returns a Specific Developer Detail
- Returns a List of Developers
However, we will add a combination of Generic Repository Pattern along with Unit Of Work to the mix to make this implementation a more logical and practical one. We will specifically identify and implement the use case of Specification Pattern here. This will be almost everything that you would need while building complete applications with ASP.NET Core 5.0. Letās get started.
PS, you can find the entire source code of this implementation here.
Setting up the Project
First up, letās open up Visual Studio 2019+ and create a new Solution and a WebAPI Project. Note that we will be following a Hexagonal Architecture in this implementation as well, to keep the solution well organized.
To learn more about Hexagonal / Onion Architecture in ASP.NET Core, please refer to this article - Onion Architecture In ASP.NET Core With CQRS ā Detailed
Once the API Project is added, letās add 2 more Class Library projects to this solution. Letās call it Data & Core.
- Data is where all the implementations related to the database and contexts live.
- Core is where we will be adding in the interfaces and domain entities.
This is how the solution would look like at this stage.
Adding the Required Model
As mentioned earlier, in your Core Project, create a new folder named Entities and add 2 classes to it, namely Developer and Address.
Adding the DBContext ,Migrations & Required Packages
Now, letās install the required NuGet packages to the respective projects.
Open up the Package Manager Console and set the Data Project as the default project from the drop-down. Run the following commands to install the required packages.
Next, set the API project as the default project, and run the following command.
Before setting up the Application Context class, letās add in the Connection String. For this, open up the appsettings.json from the API Project and add in the following.
Note that we are currently using SQLServer Local DB for this demonstration.
With that done, letās create the required context class that will help us to access the database. For this, under the Data Project, add in a new class and name it ApplicationDbContext.
Here, you can see that we are mentioning both the Developer and Address classes to be in included in the Application Db Context.
Next, we need to add this context to our ASP.NET Core Applicationās service container and configure the connection details as well. Open up the Startup.cs in the API Project and add in the following under the ConfigureServices method.
Finally, we are ready to add the migrations and update the database as well. Open up the package manager console once again and set the Data project as the default project. Run the following commands:
Here is a screenshot demonstrating the same. Note that you may receive warnings regarding the Precision of the mentioned decimal properties. We can ignore it for now.
With that done, our database should be ready now with the required tables and the corresponding fields. For demo purposes, I am adding in some sample data directly to the database using the SQL Server Object Explorer tool of Visual Studio 2019 IDE.
Implementing Generic Repository Pattern
As our requirement is to return result sets of Developers, letās create a Generic Repository Pattern so that it can use up the ApplicationDbContext to query data from the database. The importance of using a generic repository pattern is that this code can be reused for multiple other entities as well.
To get a detailed understanding of Repository Pattern, I highly recommend that you go through this article - Repository Pattern in ASP.NET Core ā Ultimate Guide
For instance, we add a new entity named Product later, you do not necessarily need to add in a new class for Accessing Product data from the database, but you can use the already existing generic repository implementation for most of the use cases. Note that there are a few limitations for generic repository patterns which we will discuss and resolve in the later section of the article.
Under the Core section, Add a new folder and name it Interfaces. Here, add in a new interface, IGenericRepository.
Note that we are only interested in the Query Operations in this Repository Implementation. Hence, we will only look in to the GetById and GetAll Methods here.
Creating Generic Repository Implementation
Now, letās implement the above created interfaces. Since we are following a Hexagonal / Onion Architecture, we will have to add the implementations outside the Core of the Application. This means, all the data related implementations are to be added in the Data Project.
Here, add a new class, GenericRepository.
Make sure to add the required project references. Data project refers to Core Project. Core Project has 0 project dependencies. API Project depends on Data as well.
You can see that we are injecting the instance of the ApplicationDbContext to the constructor of this repository Implementation. This instance is further used to read data from the Database.
Finally, in the Startup.cs of the API Project, add in the following to register the IGenericRepository interfaces to the service container of the application.
Problem with Generic Repository Pattern: Anti-Pattern?
Generic Repository is considered by some developers as an anti-pattern. If used incorrectly, yes, any pattern will mess up your code. The major complaint about Generic Repository is that a single method could potentially expose the entire database access code to the user. It could also mean the need for multiple methods for each and every combination of requirements (as mentioned in the beginning of this article). For instance, look at the following interface declaration -
This method can be a part of the Generic Repository Pattern to combat the issue we have. But since the method is too generalized, itās not possible for the Generic Repository to know about the expressions we pass to it. Another idea could be to remove this method from IGenericRepository interface and use it in a new interface, letās say, IDeveloperRepository which derives from IGenericRepository. This could work, but considering the future additions of entities and requirement changes, this change would not be a smart one to proceed with.
Imagine having 20-30 new entities and having to create tons of new repositories? Not a good idea, yeah? Think about having multiple methods in IDevloperRepository and its implementation like GetDevelopersWithSalariesGreaterThan(decimal salary) & GetDevelopersWithExperienceLessThan(int years), not clean, yeah?
What if there is a much cleaner way to tackle this requirement? This is exactly where Specification Pattern comes handy.
Enhancing Repository Pattern with Specification Pattern in ASP.NET Core
Specification Pattern may feel complex at the first glance. I felt it too. But once you have added certain Base Classes and Evaluators, all you have to do is to create Specification Classes which are usually 2 - 10 lines depending on your requirement. Letās get started with Specification Pattern In ASP.NET Core.
Under the Core Project, add a new folder and name it Specifications. This is where all the specification related interfaces would go to.
Create a new Interface and name it, ISpecification.cs
This is a minimal implementation only. Let me explain each of the declared method definitions.
- Criteria - This is where you can add in the expressions based on your entity.
- Includes - If you want to include foreign keyed table data, you could add it using this method.
- OrderBy and OrderByDescending are quite self-explanatory.
Next, in the same folder, add in a new class, BaseSpecifcation. This will be the implementation of the ISpecification Interface.
Here, we will be adding 3 essential methods and a constructor.
- Adds the expressions to the Includes property
- Adds the expressions to OrderBy Property
- Adds the expressions to OrderByDescending Property
- You can note that we also have a constructor that takes in criteria. Criteria can be like ( x=>x.Salary > 100 ) and so on. You get the point, yeah?
Upgrading the Generic Repository.
To get started, letās add a method in our IGenericRepository interface.
Next, letās implement the new method in the GenericRepository class.
Now, the idea behind setting all this up is to create separate specification classes that can return specific result sets. Each of these new specification classes will inherit from the BaseSpecification class. Get the idea? Letās create those Specification classes now so that it makes sense ;)
So, letās draw out 2 requirements / specifications:
- A specification to return list of developers in the decreasing order of salary.
- Another specification that returns a list of developers with an experience of N or above along with their addresses.
Under the same Specification folder of the Core project, letās add our first specification class, DeveloperByIncomeSpecification
Here, you can see that we are deriving from the BaseSpecification class and using the AddOrderByDescending method within the constructor. This specification would ideally return a list of developers with the decreasing order of their income.
Next, letās add another class, DeveloperWithAddressSpecification
So, here we are passing the query expression to the base of the Specification Class which is the BaseSpecificationās constructor, which in turn will add it to the Criteria property that we had created earlier. Quite simple, actually.
Now, with our specification classes ready, letās add the api endpoints.
Under the API project, add a new API Controller under the Controllers folder and name it DevelopersController.
Lines 3 - 7: Injecting IGenericRepository to the constructor of the Controller.
Lines 8 - 19: Standard endpoints that make use of the repository instance to return all the developers and the one with specific Id.
Lines 20 - 27: This is the most interesting part of the controller. Here Line 23 and 24 as you can see are the 2 specification classes we created earlier. This is just to demonstrate that any such Specification instance can be created at either the Controller or wherever the GenericRepository is being consumed. Weāll use the DeveloperWithAddressSpecification(3) for demonstration.
Letās run the application now and check the result from the specify endpoint.
You can see that the Address data is also returned. Now, go back to the controller, comment out line 24 and letās use the DeveloperByIncomeSpecification for now. Again, run the application.
Now you can notice that there no Address data being returned. Why? Simple, because we used a different specification that does not mention the addition of the Address entity. Rather this specification returns the set of developers in the decreasing order of their income. Simple, yet neat right? This is probably one of the coolest design patterns to have on your ASP.NET Core applications.
Weird, but this is actually when you can understand what Specification pattern is :P According to Wikipedia - In computer programming, theĀ specification patternĀ is a particularĀ software design pattern, wherebyĀ business rulesĀ can be recombined by chaining the business rules together using boolean logic. The pattern is frequently used in the context ofĀ domain-driven design.
Makes much more sense now, yeah? Business Rules (our requirement to return developers with a certain level of experience or higher) are combined by chaining criteria (this happened in the DeveloperWithAddressSpecification class) which is a boolean logic. Very simple, yet too powerful ;)
Moving forward, the possibilities with this pattern is quite endless and helps very much in scaling up the application. This pattern could potentially support Data-Shaping and Pagination as well. Pretty powerful pattern with a very little learning curve, yeah? Thatās a wrap for this article.
Summary
In this article, we have covered Specification Pattern in ASP.NET Core Applications and how it enhances Generic Repository Pattern by giving it an upper hand. We have also built a complete web API application that follows onion architecture for clean code management. You can also find the complete source code on my Github here. Have any suggestions or questions? Feel free to leave them in the comments section below. Thanks and Happy Coding! š