DataGrid component
1. Nuget packages
ASOL.DataGridComponent.Contracts- models and annotations for View Definitions and Filter DefinitionsASOL.DataGridComponent.Services.Common- just helper methods for generators and filtering services
DataGridQueryBuilder
ASOL.DataGridComponent.Services.Query- base query building service forIQueryable<T>ASOL.DataGridComponent.Services.Query.MongoDb- MongoDb implementation of query building serviceASOL.DataGridComponent.Services.Query.EfCore- EfCore base query building serviceASOL.DataGridComponent.Services.Query.EfCore.PostgreSql- PostgreSQL implementation fo query building serviceASOL.DataGridComponent.Services.Query.EfCore.MsSql- MsSql implementation of query building service
ChangeLog
- 0.0.1.50382-dev: Dependencies upgrade, most importantly:
MongoDB.Driver-3.4.0,ASOL.Core. ...
Generators
ASOL.DataGridComponent.Services.Generators- services that generates View Definitions and Filter Definitions
Persistence
ASOL.DataGridComponent.Services.Persistence.Common- common library for both persistence librariesASOL.DataGridComponent.Services.Persistence.EfCore- persistence library for Entity Framework, persisting filtering settings in databaseASOL.DataGridComponent.Services.Persistence.MongoDb- persistence library for MongoDB, persisting filtering settings in database
2. The Role of Metadata and Generators
DataGrid UIKit 2.0 relies on metadata to define its structure and filtering capabilities. This metadata is generated by two key components:
- ViewDefinitionGenerator: Creates column definitions for the DataGrid, using attributes on your data transfer objects (DTOs) to specify column behavior (e.g., button appearance, data source).
- FilterDefinitionGenerator: Generates metadata for the Universal Filter. It uses reflection to determine filter types but can be customized with attributes for list filters or to exclude columns.
3. Generating Metadata with Attributes
View Definition Example (Button Column)
C#
[AttributeLabel("en-US", "Button")]
[ButtonColumnType(true, nameof(ButtonIcon), nameof(ButtonColor), nameof(ButtonFilled))]
public string? ButtonValue { get; set; }
public string? ButtonIcon { get; set; }
public string? ButtonColor { get; set; }
public string? ButtonFilled { get; set; }
These attributes tell the frontend how to display the button column, including icon, color, and whether it's filled.
Filter Definition Example (List Filter and ignore)
C#
[IgnoreInFilterFilterFieldAnnotation]
public Guid Id { get; set; }
[ListFilterFieldAnnotation]
[LocalizedValueAnnotation("en-US", "Value 1", "Value1")]
[LocalizedValueAnnotation("cz-CS", "Hodnota 1", "Value1")]
[LocalizedValueAnnotation("en-US", "Value 2", "Value2")]
[LocalizedValueAnnotation("cz-CS", "Hodnota 2", "Value2")]
public string? SimpleTextValue { get; set; }
Attributes define the specific values to appear in the list filter, localized for different languages.
Special enum localization
We've implemented a special way to localize enums, instead of localizing them in the model from which the view and filter definition is generated, we can simply localize the enum like this:
C#
public enum OrderModelType
{
[AttributeLabel("en-US", "Order")]
[AttributeLabel("cz-CS", "Objednávka")]
Order,
[AttributeLabel("en-US", "Offer")]
[AttributeLabel("cz-CS", "Nabídka")]
Offer,
}
This will automatically generate localized values for ListFilters and StateColumnTypes.
4. Delivering Metadata to the Frontend
To make this metadata available, create two endpoints:
- View Definition Endpoint: Returns the JSON view definition generated by
ViewDefinitionGenerator. - Filter Definition Endpoint: Returns the JSON filter definition generated by
FilterDefinitionGenerator. Your frontend loading services can then fetch this data from these endpoints.
5. Loading Data
Request
Data loading uses a POST request with the filter data in the body. Our new input from the DataGrid into the endpoint is:
public record DataGridRequest(
int Offset = 0,
int Limit = 20,
string? NextPageToken = null,
string? SortColumn = null,
SortOrder SortOrder = SortOrder.Ascending,
FilterRequest? Filter = null);
Response
Response for data for the DataGrid is created as collection holder with some extended attributes. Model is defined as PagedResult<T>.
public record PagedResult<T>(int TotalItemsCount, IReadOnlyList<T> Items)
{
public IReadOnlyCollection<Summarization> PageSummarizations { get; init; } = Array.Empty<Summarization>();
public IReadOnlyCollection<Summarization> AllRecordsSummarizations { get; init; } = Array.Empty<Summarization>();
}
As you can see we have new summarization collection, this object is used for a special line under the grid where under the given column, a number shows up showing summarization either for the current page or for the whole grid.
6. Query building service
A dedicated service DataGridQueryBuilder<T, TKey> is created to simplify the usage of datagrid.
It does the following:
- Accepts an
IQueryable<T>and the necessary fields as criteria. - Calls the FilteringService to build expressions based on filter fields, modifies the
IQueryable<T>. - Calls the TotalCountService to asynchronously get the total count of the filtered
IQueryable<T>. - Calls the SortingService to apply sorting based on the
SortColumnandSortDirectionprovided, if theSortColumnis null it uses thedefaultSortingsprovided as a field in theBuildQuerymethod. - Applies Paging based on the
DataGridRequest.- If the
NextPageTokenis not null in theDatagridRequest, then TokenPagingService is called, to apply paging based on theNextPageTokenand the current sorting. - If the
NextPageTokenis null, thenOffsetandLimitproperties fromDataGridRequestwill be used.
- If the
- Returns the filtered, sorted and paged
DataGridQueryable<T>that can be used for database execution.
The result DataGridQueryable<T> looks like this:
C#
public class DataGridQueryable<T>
{
public IQueryable<T> Query { get; }
public long TotalCount { get; }
public DataGridQueryable(IQueryable<T> query, long totalCount)
{
Query = query;
TotalCount = totalCount;
}
}
The DataGridQueryBuilder<T, TKey> and all its parts have to be registered in DI, for that we have simple startup extensions.
Be careful, you want to use the correct startup extension, based on what data source you use, for PostgreSQL you want to use the AddQueryBuilderPostgreSql startup extension. You can use multiple data sources in one project, so you can register multiple query builders, but you have to use the correct one in your query handlers.
Wide search
Implementing wide search in BuildQuery method allows you to define custom logic for handling, when user types into the universal filter textbox, without selecting any fields. It has to be setup, and accepts string, and returns IQueryable<T>
List filter customization
Implementing filterByListPredicate in BuildQuery method allows you to define custom logic for handling GetFilterByListPredicate operator of List filter within specific QueryHandler implementations.
Hierarchical List filter customization
Implementing filterByHierarchicalListPredicate in BuildQuery method allows you to define custom logic for handling GetFilterByHierarchicalListPredicate operator of Hierarchical List filter within specific QueryHandler implementations.
Overriding default behavior of the Query Builder Service
Each step of the query building like filtering, sorting, paging and getting total count are separate services registered in DI.
You can inherit any of these and rewrite how they work.
Model mapping
Explicit mapping definition
If you use different models for persistence layer and for API endpoints, use mapping to define what property of the persistence layer should be used based on property name of the model.
The map can be any collection of ModelAttributeMapItem<T> type where the string is property name of the API endpoint model and the func is a lambda using the persistence model.
The lambda is then sent into the DataGridQueryBuilder<T, TKey> to be used in the query building process, be careful that in the end, a specific database engine will be used (MongoDb.Driver does not support complex lambdas, usually just simple accessors).
var map = new List<ModelAttributeMapItem<TestModel>>()
{
new ModelAttributeMapItem<TestModel>("Name", model => model.Name),
new ModelAttributeMapItem<TestModel>("Name", model => model.Name + " " + model.Surname),
new ModelAttributeMapItem<TestModel>("Hours", model => model.Minutes / 60),
};
Automatic mapping
If incoming property name is not found in the map defined above using ModelAttributeMapItem<T> then property is mapped automatically to persistence model based on property names.
Sorting by multiple columns
If you are displaying multiple data source columns in one DataGrid column and what to sort by more than one of them you have to define sorting map.
The map can be any collection of ModelAttributeSortingMapItem<T> type where the string is property name of the API endpoint model and the Sortings is a list of SortItem<T> where the func is a lambda using the persistence model and the SortOrder is the sorting order for that lambda.
var sortingMap = new List<ModelAttributeSortingMapItem<TestModel>>()
{
new ModelAttributeSortingMapItem<TestModel>("FullName", new List<SortItem<TestModel>>()
{
new SortItem<TestModel>(model => model.FirstName, SortOrder.Ascending),
new SortItem<TestModel>(model => model.Surname, SortOrder.Descending)
}),
};
Sorting map has precedence over model mapping when sorting, so if a property is found in both maps, the sorting map will be used.
7. DataGridQueryBuilder example
Frontend model:
public class ExampleModel
{
[IgnoreInColumnDefinition]
[IgnoreInFilterFilterFieldAnnotation]
public Guid Id { get; set; }
[AttributeLabel("cs-CZ", "Jméno")]
[AttributeLabel("en-US", "Name")]
public string Name { get; set; }
[AttributeLabel("cs-CZ", "Jméno dítěte")]
[AttributeLabel("en-US", "Child name")]
public string ChildName { get; set; }
}
Backend entities:
public class ExampleParentEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public ExampleChildEntity Child { get; set; }
}
public class ExampleChildEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid ParentId { get; set; }
}
Mapping frontend filter values to backend entity selectors:
ICollection<ModelAttributeMapItem<ExampleParentEntity>> map = new List<ModelAttributeMapItem<ExampleParentEntity>>()
{
new ("Id", entity => entity.Id),
new ("Name", entity => entity.Name),
new ("ChildName", entity => entity.Child.Name)
};
Sorting map for sorting by multiple columns:
ICollection<ModelAttributeSortingMapItem<ExampleParentEntity>> sortingMap = new List<ModelAttributeSortingMapItem<ExampleParentEntity>>()
{
new ("Name", new List<SortItem<ExampleParentEntity>>()
{
new SortItem<ExampleParentEntity>(model => model.Surname, SortOrder.Ascending),
new SortItem<ExampleParentEntity>(model => model.Name, SortOrder.Ascending)
})
};
Applying the DataGridQueryBuilder when querying, your query handlers will be different:
public IQueryable<ExampleParentEntity> QueryData(
DataGridRequest request,
IDataGridQueryBuilder<ExampleParentEntity, Guid> datagridQueryBuilder,
ICollection<ModelAttributeMapItem<ExampleParentEntity>> map,
ICollection<ModelAttributeSortingMapItem<ExampleParentEntity>> sortingMap,
CancellationToken ct)
{
IQueryable<ExampleParentEntity> query = GetQueryable<ExampleParentEntity>();
var gridQuery = datagridQueryBuilder.BuildQuery(
originalQuery: query,
dataGridRequest: request,
filterByWideSearch: (query, search) => query.Where(x => x.Name.Contains(search) || x.Surname.Contains(search)),
defaultSortings: new List<SortItem<ExampleParentEntity>>() {new SortItem<ExampleParentEntity>(x => x.Surname, SortOrder.Descending)},
cancellationToken: ct,
modelAttributeMap: map,
modelAttributeSortingMap: sortingMap);
}
8. Localization support
Localization using LocalizedValue<> is supported in the EFCore query builder service, so you can use localized values in your queries.
The query to get the localized value is quite complex model => model.LocalizedStringValue.Values!.FirstOrDefault(x => x.Locale == "en-US")!.Value MongoDb driver does not support this kind of complex queries, so for now it's unsupported and manual mapping is required.
To get the localized value, we use languageCode, when calling DataGridQueryBuilder<T, TKey>.BuildQuery method, it will be used to get the localized value from the LocalizedValue<> property. It is optional, if not provided, we'll get the language from the IRuntimeContext service.
9. Available contract types
DataGrid
DataGridRequest- Request model with parameters for filtering, sorting and paging data used in DataGrid.PagedResult<T>- Response model with data items used in DataGrid. Contains information about items count and summarizations of current page and all records.DataGridQueryable<T>- Filtering result, containsIQueryable<T> Queryandlong TotalCountfrom the filtered query before paging is applied.SortItem<T>- Sorting model used in Sorting Service for default sorting ifSortColumnandSortOrderare not set and for custom sorting by multiple columns.
DataGrid Definition
DataGridDefinitionModel- Definition of entire DataGrid.
View Definition
Models
ViewDefinitionModel- Definition of concrete View of DataGrid.ColumnDefinitionModel- Definition of a column in concrete View of DataGrid. Contains definition of column template specific to the type.
Enums
BadgeVariantBooleanInputTypeCellTypeColumnPinHorizontalAlignmentQuickViewVisibility
Column templates
BadgeColumnTemplateDefinitionModelBooleanColumnTemplateDefinitionModelButtonColumnTemplateDefinitionModelDateColumnTemplateDefinitionModelDocumentColumnTemplateDefinitionModelIconColumnTemplateDefinitionModelImageColumnTemplateDefinitionModelLinkColumnTemplateDefinitionModelMoneyColumnTemplateDefinitionModelMultilineTextColumnTemplateDefinitionModelNumberColumnTemplateDefinitionModelProgressColumnTemplateDefinitionModelSimpleTextColumnTemplateDefinitionModelStateColumnTemplateDefinitionModelTimeColumnTemplateDefinitionModelToggleButtonColumnTemplateDefinitionModel
DataGrid Annotations
Annotations for entire model
ViewParametersAttribute- Parameters of DataGrid View.
Annotations for model properties
-
General
ColumnHorizontalAlignmentAttribute- Horizontal alignment of values in the column.ColumnIsVisibleInQuickViewPanelAttribute- Defines whether this property is visible as attribute in Quick View Panel.ColumnSettingsAttribute- Multiple parameters applied to the column. Only some of them can be set, default is used for others.ColumnSummarizationAttribute- Specifies that summarization should be calculated for defined property and column should display that summarization.IgnoreInColumnDefinitionAttribute- Column should not be generated and displayed for defined property.OrderIndexAttribute- Order of the column in DataGrid View.VisibilityAttributeAttribute- Specifies which property (AttributeName) is used to set visibility of current property. Referenced property must be defined as Boolean cell type.
-
Column types
BadgeColumnTypeAttributeBooleanColumnTypeAttributeButtonColumnTypeAttributeDateColumnTypeAttributeDocumentColumnTypeAttributeIconColumnTypeAttributeImageColumnTypeAttributeLinkColumnTypeAttributeMoneyColumnTypeAttributeMultilineTextColumnTypeAttributeNumberColumnTypeAttributeProgressColumnTypeAttributeSimpleTextColumnTypeAttributeStateColumnTypeAttributeTimeColumnTypeAttributeToggleButtonColumnTypeAttribute
Filter Definition
-
FilterDefinitionModel- Definition of filter and its fields. -
Fields
DateFilterFieldDefinitionModelHierarchicalListFilterFieldDefinitionModelListFieldFilterDefinitionItemModelListFilterFieldDefinitionModelNumberFilterFieldDefinitionModelTextFilterFieldDefinitionModel
Filter Instance
Request model
FilterRequest- Definition of filtering criteria.FilterFieldRequest- Definition of filtering field.
Model for filter processing
FilterInstanceModel- Instance of filter with its fields.
Filter fields
-
Text
EndsWithTextFilterFieldModelEqualTextFilterFieldModelExcludesTextFilterFieldModelFilledTextFilterFieldModelIncludesTextFilterFieldModelNotEqualTextFilterFieldModelStartsWithTextFilterFieldModelTextFieldOperatorTextFilterFieldModelUnfilledTextFilterFieldModel
-
Number
EqualNumberFilterFieldModelFilledNumberFilterFieldModelGreaterThanNumberFilterFieldModelGreaterThanOrEqualNumberFilterFieldModelLessThanNumberFilterFieldModelLessThanOrEqualNumberFilterFieldModelNotEqualNumberFilterFieldModelNumberFieldOperatorNumberFilterFieldModelRangeNumberFilterFieldModelUnfilledNumberFilterFieldModel
-
List
FilledListFilterFieldModelIncludesListFilterFieldModelExcludesListFilterFieldModelListFieldOperatorListFilterFieldModelUnfilledListFilterFieldModel
-
Hierarchical List
FilledHierarchicalListFilterFieldModelHierarchicalListFieldOperatorHierarchicalListFilterFieldModelIncludesHierarchicalListFilterFieldModelExcludesHierarchicalListFilterFieldModelUnfilledHierarchicalListFilterFieldModel
-
Date
DateFieldOperatorDateFilterFieldModelExactDateDateOnlyFilterFieldModelExactDateDateTimeFilterFieldModelExactMonthDateFilterFieldModelExactQuarterFieldDateFilterModelExactYearDateFilterFieldModelFilledDateFilterFieldModelRangeDateDateOnlyFilterFieldModelRangeDateDateTimeFilterFieldModelUnfilledDateFilterFieldModelYesterdayDateFilterFieldModel
Filter Annotations
Annotations used for model properties to specify filter fields (field type, operators, etc.).
DateFilterFieldAnnotationAttributeHierarchicalListFilterFieldAnnotationAttributeIgnoreInFilterFilterFieldAnnotationAttributeListFilterAttributeValueListFilterFieldAnnotationAttributeListFilterFieldValueAnnotationAttributeNumberFilterFieldAnnotationAttributeTextFilterFieldAnnotationAttribute
General annotations
AttributeLabelAttribute- Specifies label of attribute used for defined property. Is used as column header in DataGrid and attribute name in field of Universal Filter.
Services
Generators
IFilterDefinitionGenerator,FilterDefinitionGenerator- Service that generates metadata (Filter Definition) for given model based on annotation attributes and reflection.IViewDefinitionGenerator,ViewDefinitionGenerator- Service that generates metadata (View Definitions) with column definitions for given model based on annotation attributes and reflection.
Helpers
IQueryBuilderHelper,QueryBuilderHelper- helper methods for generating expressions
Mappers
IFilterRequestMapper,FilterRequestMapper- Maps receivedFilterRequesttoFilterInstanceModeland all fields based on filter types.
DataGridQueryBuilder
IDataGridQueryBuilder<T, TKey>,DataGridQueryBuilder<T, TKey>- Query builder service (see 6. Query builder service).IFilteringService<T>,FilteringService<T>- Service that calls:IDateFilteringService<T>,DateFilteringService<T>- Filtering service for Date filtersINumberFilteringService<T>,NumberFilteringService<T>- Filtering service for Number filtersITextFilteringService<T>,TextFilteringService<T>- FilteringService for Text filtersIDateTimeProvider,DateTimeProvider- Datetime provider used in Date filtersIOffsetLimitPagingService<T>,OffsetLimitPagingService<T>- Paging service usingOffsetandLimitITokenPagingService<T, TKey>,TokenPagingService<T, TKey>- Token based paging service usingNextPageTokenISortingService<T, TKey>,SortingService<T, TKey>- Sorting serviceITotalCountService<T>,TotalCountService<T>- Service to get total count ofIQueryable<T>, the implementation is database backend specific so it can be async, meaning EFCore and MongoDb has specific implementations
Note: There are multiple implementations of the IDataGridQueryBuilder<T, TKey> and all the services mentioned above to support using different data sources in parallel in one project. E.g. IEFCoreDataGridQueryBuilder<T, TKey>, IEFCoreFilteringService<T>...
Extensions, Helpers and other
StartupExtensions- register services to DIExpressionExtensions- extensions used in building expressions, like Replace, which traverses the expression and replaces parts of it