What is Population.NET?
December 25, 2024 ยท View on GitHub
Population.NET
What is Population.NET?
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.
Main Features
- 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.
โณ Installation
Using Package Manager
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" />
Using .NET CLI
dotnet add package Population.NET --version 1.8.1
๐ Requirements
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.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))
๐ Example Project!
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
๐ Usage Example
๐ฅ Built-in BaseEntity Support
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.
๐ฅ QueryContext Class
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();
}
๐ฅ Simple Population
-
Populate specific relations and fields
Normally
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.
โก Using population
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); }๐ฅ 1. Fields Selection
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.
Example Use Cases
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๐ฅ 2. Without populate
Without the
populateparameter, aGETrequest will only return the default fields and will not include any related data.Example Request:
GET /api/User/SimplePopulationExample Response:
[ { "name": "John Doe", "email": "john.doe@example.com", "userName": "johndoe123", "password": "Password@123", "status": 1, "id": "74850000-9961-b42e-a80d-08dd1e75109d", "createdAt": "2024-12-17T08:30:29.463949+00:00" }, ... ]๐ฅ 3. Populate all relations and fields, 1 level deep
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": "john.doe@example.com", "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 1๐ฅ 4. Populate specific relations and fields
You 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
Populate fields and relationships at 1 level deep
Example parameter syntax populate=rolepopulate[role]=truepopulate[role]=*populate[Role][fields]=nameNote
The first three lines have different syntax but the same result.
Populate fields and relationships at a depth greater than 1 level
Example parameter syntax populate[role][populate]=permissionspopulate[role][populate][permissions]=truepopulate[role][populate][permissions]=* -
๐ฅ Population with Filters, Search, Sort, and Paging
-
To use Population with Filters, Search, Sort, and Paging, we will utilize the
CompileQueryAsyncextension method instead ofProjectDynamic.Example usage:
[HttpGet("PopulationWithDataManipulation")] public async Task<IActionResult> GetAllWithSimplePopulationWithDataManipulationAsync([FromQuery] QueryContext queryContext) { PaginationResponse<dynamic> response = await context.Users.CompileQueryAsync<UserResponse>(queryContext, mapper); return Ok(response); }Pagination
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]=15Searching
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]=emailNote
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.
Sorting
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:descNote
:ascis default order, can be omittedFiltering
Queries can accept a
filtersparameter with the following syntax:GET /api/:pluralApiId?filters[field][operator]=valueThe 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
Contributing
Contributions are welcome! Feel free to submit a pull request or open an issue to discuss any changes or improvements.
License
This project is licensed under the MIT License. See the LICENSE file for more details.
Authors
- Authentic
Acknowledgements
Population.NET seamlessly integrates with: