Population.NET is a .NET library designed to optimize data retrieval from the server, maximizing performance when clients make API calls. It allows clients to specify the exact fields they need, reducing unnecessary data transfer by avoiding the retrieval of all fields by default.
Inspired by the populate feature in Strapi for Node.js, Population.NET brings similar capabilities to .NET, enhancing API flexibility and efficiency.
Additionally, the library includes essential data manipulation features such as filters, search, sort, and paging, all designed to handle complex data types like objects and collections.
With Population.NET, you can effortlessly build powerful and efficient APIs that meet the demands of modern applications.
- Built-in BaseEntity Support: Provides a built-in abstract
BaseEntity
class to simplify entity creation - QueryContext: Provides a common query params request class for search APIs.
- Simple Population: Easily retrieve and populate data with a simple and intuitive API, inspired by Strapi's populate feature.
- Population with Filters, Search, Sort, and Paging: Combine population capabilities seamlessly with filtering, searching, sorting, and pagination to handle complex data queries efficiently.
To install Population.NET in using Using Package Manager, follow these steps:
- Open Visual Studio 2022.
- Go to Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution....
- Search for
Population.NET
in the Browse tab and install the package.
or
Add the following package reference to your project file:
<PackageReference Include="Population.NET" Version="1.8.1" />
dotnet add package Population.NET --version 1.8.1
To use Population.NET, ensure the following requirements are met:
-
Using .NET 8 or higher:
Make sure your project is targeting .NET 8 or a newer version. You can set the target framework in your.csproj
file:<TargetFramework>net8.0</TargetFramework>
-
AutoMapper Configuration: Configure AutoMapper in your project to handle object mapping. Below is a simple example of how to set up AutoMapper:
using AutoMapper; public class MappingProfile : Profile { public MappingProfile() { // Example mapping configuration CreateMap<Entity, Response>(); } }
Then, register the mapping configuration in your project (e.g., in Program.cs):
var mapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile<MappingProfile>(); }); IMapper mapper = mapperConfig.CreateMapper(); builder.Services.AddSingleton(mapper);
or
builder.Services.AddAutoMapper(typeof(ProfileAssemblyType))
To help you get started with Population.NET, we have prepared an example project that demonstrates its key features and best practices.
π₯ Download or clone the project now from GitHub:
π Example-Population GitHub Repository
Population.NET provides a built-in abstract BaseEntity
class to simplify entity creation. It supports automatic ID generation and creation timestamps.
public abstract class BaseEntity : BaseEntity<Guid>, IGuidIdentify
{
protected BaseEntity() => Id = NewId.Next().ToGuid();
}
public abstract class BaseEntity<TId> : IEntity<TId>
{
public TId Id { get; set; } = default!;
public virtual DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}
Note:
When building entity models using the integrated BaseEntity, if theCompileQueryAsync
extension method is used without specifying sorting, the results will be sorted by CreatedAt: Desc by default.
A common query params request class, using for APIs integrate with Population.NET
public class QueryContext
{
public PagingDescriptor Pagination { get; set; } = new();
public List<SortDescriptor>? Sort { get; set; }
public List<FilterDescriptor>? Filters { get; set; }
public SearchDescriptor? Search { get; set; }
public PopulateDescriptor Populate { get; set; } = new();
}
-
Populate specific relations and fields
We will create a simple GET API to fetch all users using AutoMapper's
ProjectTo
method.[HttpGet("UsingProjectTo")] public async Task<IActionResult> GetAllAsync() { List<UserResponse> response = await context.Users ProjectTo<UserResponse>(mapper.ConfigurationProvider) .ToListAsync(); return Ok(response); }
By default, when using ProjectTo from AutoMapper without additional filtering, the data returned will include all properties defined in the response DTOs class. This behavior can lead to overfetching of data, especially when the response class contains nested relationships or unnecessary fields.
Create GET API to fetch all users using AutoMapper's
ProjectDynamic
method.[HttpGet("SimplePopulation")] public async Task<IActionResult> GetAllWithSimplePopulationAsync([FromQuery] QueryContext queryContext) { List<dynamic> response = await context.Users .ProjectDynamic<UserResponse>(mapper, queryContext.Populate) .ToListAsync(); return Ok(response); }
Queries can accept a
fields
parameter to select only specific fields. By default, only the following types of fields are returned:-
String types: string, uuid, ...
-
Date types: DateTime, DateTimeOffset, ....
-
Number types: integer, long, float, and decimal.
-
Generic types: boolean, array of primitive types.
Use case Example parameter syntax Select a single field fields=name
Select multiple fields fields=name&fields=Email
Select populate and fields populate[Role][fields]=name
Note: Field selection does not work on relational. To populate these fields, use the
populate
parameter.
Example Request: Return only name, description, Role.Name fields
GET /api/User/SimplePopulation?fields=name&fields=Email&populate[Role][fields]=name
Without the
populate
parameter, aGET
request will only return the default fields and will not include any related data.Example Request:
GET /api/User/SimplePopulation
Example Response:
[ { "name": "John Doe", "email": "[email protected]", "userName": "johndoe123", "password": "Password@123", "status": 1, "id": "74850000-9961-b42e-a80d-08dd1e75109d", "createdAt": "2024-12-17T08:30:29.463949+00:00" }, ... ]
You can return all fields and relations. For relations, this will only work 1 level deep, to prevent performance issues and long response times.
To populate everything 1 level deep, add the
populate=*
parameter to your query.Example Request:
GET /api/User/SimplePopulation?populate=*
Example Response:
[ { "name": "John Doe", "email": "[email protected]", "userName": "johndoe123", ... "role": { "name": "Admin", "description": "Administrator role with full access", "id": "74850000-9961-b42e-baae-08dd1e75109d", "createdAt": "2024-12-17T08:30:29.525732+00:00" } }, ... ]
Note: If your data includes additional relationships beyond
role
, such asorganization
, orgroups
using thepopulate=*
parameter will also include those relationships as long as they are at a depth of 1You can also
populate
specific relations and fields, by explicitly defining what to populate. This requires that you know the name of fields and relations to populate.Note: Relations and fields populated this way can be 1 or several levels deep
Example parameter syntax populate=role
populate[role]=true
populate[role]=*
populate[Role][fields]=name
Note: The first three lines have different syntax but the same result.
Example parameter syntax populate[role][populate]=permissions
populate[role][populate][permissions]=true
populate[role][populate][permissions]=*
-
-
To use Population with Filters, Search, Sort, and Paging, we will utilize the
CompileQueryAsync
extension method instead ofProjectDynamic
.[HttpGet("PopulationWithDataManipulation")] public async Task<IActionResult> GetAllWithSimplePopulationWithDataManipulationAsync([FromQuery] QueryContext queryContext) { PaginationResponse<dynamic> response = await context.Users.CompileQueryAsync<UserResponse>(queryContext, mapper); return Ok(response); }
To paginate results by page, use the following parameters:
Parameter Type Description Default pagination[page] Integer Page number 1 pagination[pageSize] Integer Page size 10 Example Request:
GET /api/User/PopulationWithDataManipulation?pagination[page]=1&pagination[pageSize]=15
To search for data, use the following parameters:
Parameter Type Description Default search[keyword] String Search keyword null
search[fields] Array(String) A collection fields search null
Example Request:
GET /api/User/PopulationWithDataManipulation?search[keyword]=Jane&search[fields]=userName&search[fields]=email
Note:
If a search keyword is used but no specific search fields are provided, the search will apply to all selected fields except for fields of the following types:Enum
Guid
Boolean
TimeOnly
- Fields marked with the
NotSearchAttribute
.
To sort data by one or multiple fields, pass sort parameters using array syntax:
Example Request:
GET /api/User/PopulationWithDataManipulation?sort[0]=createdAt:asc&sort[1]=name:desc
Note:
:asc
is default order, can be omittedQueries can accept a
filters
parameter with the following syntax:GET /api/:pluralApiId?filters[field][operator]=value
The following operators are available:
Operator Description $eq
Equal $ne
Not equal $lt
Less than $lte
Less than or equal to $gt
Greater than $gte
Greater than or equal to $in
Included in an array $notIn
Not included in an array $contains
Contains $notContains
Does not contain $null
Is null $notNull
Is not null $startsWith
Starts with $notstartsWith
Not start with $endsWith
Ends with $notendsWith
Not Ends with Example Request:
GET /api/User/PopulationWithDataManipulation?filters[username][$eq]=janesmith456
$in
orperatorGET /api/User/PopulationWithDataManipulation?filters[status][$in][0]=1&filters[status][$in][1]=2
Note::
null
operator is not available at the moment
Contributions are welcome! Feel free to submit a pull request or open an issue to discuss any changes or improvements.
This project is licensed under the MIT License. See the LICENSE file for more details.
- Authentic
Population.NET seamlessly integrates with: