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
BaseEntityclass 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.NETin 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.1To 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.csprojfile:<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 the CompileQueryAsync 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
ProjectTomethod.[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
ProjectDynamicmethod.[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
fieldsparameter 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=nameSelect multiple fields fields=name&fields=EmailSelect populate and fields populate[Role][fields]=name
[!NOTE] Field selection does not work on relational. To populate these fields, use the
populateparameter.Example Request: Return only name, description, Role.Name fields
GET /api/User/SimplePopulation?fields=name&fields=Email&populate[Role][fields]=name
Without the
populateparameter, aGETrequest 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, orgroupsusing thepopulate=*parameter will also include those relationships as long as they are at a depth of 1You can also
populatespecific 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=rolepopulate[role]=truepopulate[role]=*populate[Role][fields]=name[!NOTE] The first three lines have different syntax but the same result.
Example parameter syntax populate[role][populate]=permissionspopulate[role][populate][permissions]=truepopulate[role][populate][permissions]=* -
-
To use Population with Filters, Search, Sort, and Paging, we will utilize the
CompileQueryAsyncextension 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 nullsearch[fields] Array(String) A collection fields search nullExample 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:
EnumGuidBooleanTimeOnly- 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]
:ascis default order, can be omittedQueries can accept a
filtersparameter with the following syntax:GET /api/:pluralApiId?filters[field][operator]=value
The following operators are available:
Operator Description $eqEqual $neNot equal $ltLess than $lteLess than or equal to $gtGreater than $gteGreater than or equal to $inIncluded in an array $notInNot included in an array $containsContains $notContainsDoes not contain $nullIs null $notNullIs not null $startsWithStarts with $notstartsWithNot start with $endsWithEnds with $notendsWithNot Ends with Example Request:
GET /api/User/PopulationWithDataManipulation?filters[username][$eq]=janesmith456
$inorperatorGET /api/User/PopulationWithDataManipulation?filters[status][$in][0]=1&filters[status][$in][1]=2
[!NOTE]
nulloperator 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:
