Table of Contents

Filter

The Filter class represents filters that are converted to OData format before API calls. It allows you to create complex filter conditions using logical operators such as AND and OR.

A simple filter matches a single property against a value. The Filter constructor takes three parameters:

  1. Property path: The path to the property to filter on.
  2. Operator: The comparison operator (e.g., Equal, Contains, GreaterOrEqual).
  3. Value: The value to compare against.

The following example finds all customers from France:

Filter filter = new Filter("Customer.Country", FilterOperator.Equal, "France");

AND condition

To combine multiple conditions with AND logic, pass two filters to the Filter constructor. The following example retrieves all orders with a total amount between 100 and 500:

Filter orderTotalLowerBoundFilter = new Filter("Order.TotalAmount", FilterOperator.GreaterOrEqual, 100);
Filter orderTotalUpperBoundFilter = new Filter("Order.TotalAmount", FilterOperator.LessOrEqual, 500);

Filter finalFilter = new Filter(orderTotalLowerBoundFilter, orderTotalUpperBoundFilter);

OR condition

To combine conditions with OR logic, use the Join method with FilterLogicalOperator.Or. The following example finds customers from France with completed orders, or customers from Spain with pending orders:

Filter franceFilter = new Filter("Customer.Country", FilterOperator.Equal, "France");
Filter completedOrdersFilter = new Filter("Order.Status", FilterOperator.Equal, "Completed");
Filter spainFilter = new Filter("Customer.Country", FilterOperator.Equal, "Spain");
Filter pendingOrdersFilter = new Filter("Order.Status", FilterOperator.Equal, "Pending");

Filter franceAndCompleted = franceFilter.Join(completedOrdersFilter, FilterLogicalOperator.And);
Filter spainAndPending = spainFilter.Join(pendingOrdersFilter, FilterLogicalOperator.And);

Filter finalFilter = franceAndCompleted.Join(spainAndPending, FilterLogicalOperator.Or);

Handling filters by code

The Filter class provides methods to search and modify filter trees programmatically:

  • Find(propertyPath): Searches for a filter with a specific property path within the current filter or its children.
  • Remove(filter): Removes a specific filter from the current filter or its children recursively.

Example: Finding and removing a filter

Imagine you have a filter that retrieves customers from France and Spain who have completed orders. You want to find the filter related to Spain in order to modify or remove it.

  1. Create the initial filters for customers from France and Spain with completed orders:
Filter franceFilter = new Filter("Customer.Country", FilterOperator.Equal, "France");
Filter spainFilter = new Filter("Customer.Country", FilterOperator.Equal, "Spain");
Filter completedOrdersFilter = new Filter("Order.Status", FilterOperator.Equal, "Completed");
  1. Combine the filters using the Join method:
Filter franceAndCompleted = franceFilter.Join(completedOrdersFilter, FilterLogicalOperator.And);
Filter spainAndCompleted = spainFilter.Join(completedOrdersFilter, FilterLogicalOperator.And);
Filter finalFilter = franceAndCompleted.Join(spainAndCompleted, FilterLogicalOperator.Or);
  1. Find the filter related to Spain using the Find method:
Filter? foundSpainFilter = finalFilter.Find("Customer.Country");
  1. Remove the filter related to Spain using the Remove method:
finalFilter.Remove(spainFilter);

Filter states

Filters added by code can be hidden, read-only, or inactive.

Hidden filters

Set Visible to false to hide a filter from the user. The filter remains applied, but the user cannot view or modify it:

Filter filter = new Filter("Type", FilterOperator.Equal, "Client");
filter.Visible = false;
SetFilter(filter);

Read-only filters

Set ReadOnly to true to display a filter without allowing the user to modify it:

Filter filter = new Filter("Type", FilterOperator.Equal, "Client");
filter.ReadOnly = true;
SetFilter(filter);

Inactive filters

Set Active to false to keep a filter configured without applying it to the search:

Filter filter = new Filter("Type", FilterOperator.Equal, "Client");
filter.Active = false;
SetFilter(filter);

Users can activate or deactivate a visible, editable filter from its chip.

Custom views

Custom views persist filters that users can modify.

Hidden and read-only filters are not persisted: when a user applies a custom view, they keep the values defined by code.

Inactive filters are persisted, including their inactive state.

Adding filters in event rules

Two UI view event rules are commonly used to add filters by code: Initialized and Retrieving. Choose between them depending on whether the filter value is fixed for the whole lifetime of the screen or can change between two data requests.

Setting a filter once in Initialized

The Initialized rule runs once when the UI view opens. Use it only for filters whose value is known when the view opens and does not change while the screen is displayed. This covers both static filters and filters computed from a dynamic value evaluated at open time:

// Static default filter
SetFilter(new Filter("Status", FilterOperator.Equal, "Open"));
// Filter computed from a dynamic value known when the view opens
SetFilter(new Filter("CreatedBy", FilterOperator.Equal, ApplicationContext.UserIdentifier));

A filter added in Initialized behaves like a filter the user set: it is editable, and because custom views are applied after the Initialized rule, it can be replaced by the filter stored in the selected custom view.

Choose the filter state according to your intent:

  • Leave the filter editable when it is only a default value that the user is allowed to change or override.
  • Set the filter hidden or read-only when it is a technical constraint that the user must not change and that a custom view must not override.
Note

Use Initialized only for filters that stay valid for the whole lifetime of the screen. If the filter value depends on something that changes while the screen is open, add it in Retrieving instead.

Adding or updating a filter in Retrieving

The Retrieving rule runs before every data request, each time data is loaded or refreshed. Use it for technical filters whose value can change between two requests because it depends on something that changes, such as the selection in another view, a shared state, or the current time.

How you add the filter depends on whether its value is constant:

  • For a constant technical filter, add it only once by guarding with Find, so each refresh does not append another identical condition:
if (Datafilter.Find("Type") == null)
{
    Filter filter = new Filter("Type", FilterOperator.Equal, "Client");
    filter.Visible = false;
    Datafilter.Join(filter, FilterLogicalOperator.And);
}
  • For a filter whose value changes on each request, remove the previous condition first, then add the current one. Guarding with Find alone is not enough here because it would keep the outdated value:
Filter? previous = Datafilter.Find("CustomerId");
if (previous != null)
{
    Datafilter.Remove(previous);
}

Filter filter = new Filter("CustomerId", FilterOperator.Equal, GetCurrentCustomerId());
filter.Visible = false;
Datafilter.Join(filter, FilterLogicalOperator.And);

Technical filters added in Retrieving are generally hidden so the user neither sees nor edits a condition that is managed entirely by code.

Warning

A filter that must always be applied, such as a security constraint, must be enforced on the server side. A direct API call can bypass a filter set on the client.

Filtering execution side

By default, filtering is applied on the server side.

When a collection is embedded for reading, filtering is applied on the client side because all data is loaded.

You can also explicitly enable client-side filtering by setting FilteringBehavior.ExecutionSide to FilteringExecutionSide.Client (for example in an Initialized event rule):

FilteringBehavior.ExecutionSide = FilteringExecutionSide.Client;
Warning

Enable client-side filtering only for small datasets, and do not enable it when all data is not loaded on the client side.

Filtering constraints and restrictions

You can configure constraints and restrictions on filters to control user behavior:

  • Mandatory filters: Require users to provide a filter value before loading data.
  • Operator restrictions: Limit which filter operators are available for a property.

Mandatory filters

Set FilterRequired to true to require users to provide a filter value for a property:

Properties.MyProperty.FilterRequired = true;
Warning

When requiring a filter, the UI view should also have the header LoadDataOnStart set to false otherwise an error will show as soon as the UI view opens.

Operator restrictions

You can restrict available filter operators in two ways:

  • Exclude specific operators: Remove unwanted operators from the default set.
  • Specify allowed operators: Define an explicit list of permitted operators.

Excluding specific operators:

Properties.MyProperty.RemoveFilterOperators([ FilterOperator.Between, FilterOperator.In, FilterOperator.NotIn ]);

Specifying allowed operators explicitly:

Properties.MyProperty.FilterOperators = [ FilterOperator.Equal, FilterOperator.Contains, FilterOperator.StartsWith ];
Warning

Adding an incompatible or unsupported FilterOperator for the property type will cause an error at runtime.

Custom operators

Custom operators allow you to define reusable filter logic with a user-friendly display. In UI code, you can add custom operators to a property using the CustomFilterOperator class.

The CustomFilterOperator constructor takes three required parameters:

  1. Identifier: A unique string identifier for the operator.
  2. Caption: The text displayed in the operator dropdown.
  3. Filter condition factory: An async function that returns the FilterCondition to apply.

Optional properties can be set via object initializer:

  • IconId: Icon displayed in the operator dropdown.
  • GetText: Function returning the text displayed in the filter chip when active.
CustomFilterOperator greaterThan80kOperator = new CustomFilterOperator(
    "GreaterThan80k",
    "Greater than 80k",
    async () => new FilterCondition(Properties.Salary.Name, FilterOperator.GreaterOrEqual, 80000))
    {
        IconId = ImageId.FromName("greaterOrEqual"),
        GetText = () => "Salary greater than 80k",
    };

Properties.Salary.CustomFilterOperators.Add(greaterThan80kOperator);

Adding custom operators globally

You can add a custom operator to all UI views via the UIViewEvents.Initialized global event rule:

UIViewEvents.Initialized.AddHandler((args) =>
{
    IReadOnlyCollection<IViewModelProperty> filterableProperties = args.ViewModel.GetFilterableProperties();
    IViewModelProperty? createdByProperty = filterableProperties.FirstOrDefault(p => p.Name == "CreatedBy");
    if (createdByProperty != null)
    {
        createdByProperty.CustomFilterOperators.Add(new CustomFilterOperator(
            "Me",
            UIResources.Core.Me,
            async () => new FilterCondition("CreatedBy", FilterOperator.Equal, ApplicationContext.UserIdentifier))
            {
                GetText = () => UIResources.Core.CreatedByMe,
            });
    }
});

In this example, a Created by me custom operator is added to all UI views that have a filterable CreatedBy property.

Named filters

Named filters are preset filter configurations that users can quickly apply. In UI code, it's possible to add named filters using the NamedFilter class.

The NamedFilter constructor takes three parameters:

  1. Identifier: A unique string identifier for the filter.
  2. Caption: The display text shown to the user.
  3. Filter factory: An async function that returns the Filter to apply.
NamedFilter fullTimeEmployeesFilter = new NamedFilter(
    "FullTimeEmployees",
    Resources.Datagrid.FullTimeEmployees,
    async () => new Filter(Properties.EmployeeType.Name, FilterOperator.Equal, EmployeeType.FullTime));
NamedFilters.Add(fullTimeEmployeesFilter);

Named filters can combine multiple conditions:

NamedFilter fullTimeEmployeeWithSalaryGreaterThan80kFilter = new NamedFilter(
    "FullTimeEmployeesWithSalaryGreaterThan80k",
    Resources.Datagrid.FullTimeEmployeesWithSalaryGreaterThan80k,
    async () =>
    {
        Filter filter = new();
        filter.Add(new Filter(Properties.EmployeeType.Name, FilterOperator.Equal, EmployeeType.FullTime));
        filter.Add(new Filter(Properties.Salary.Name, FilterOperator.GreaterOrEqual, 80000));
        return filter;
    });
NamedFilters.Add(fullTimeEmployeeWithSalaryGreaterThan80kFilter);

It's also possible to use custom operators within named filters:

NamedFilter fullTimeEmployeeWithSalaryGreaterThan80kFilter = new NamedFilter(
    "FullTimeEmployeesWithSalaryGreaterThan80k",
    Resources.Datagrid.FullTimeEmployeesWithSalaryGreaterThan80k,
    async () =>
    {
        Filter filter = new();
        filter.Add(new Filter(Properties.EmployeeType.Name, FilterOperator.Equal, EmployeeType.FullTime));
        filter.Add(new Filter(await greaterThan80kOperator.GetFilterConditionAsync()));
        return filter;
    });
NamedFilters.Add(fullTimeEmployeeWithSalaryGreaterThan80kFilter);

Adding named filters globally

You can add a named filter to all UI views via the UIViewEvents.Initialized global event rule:

UIViewEvents.Initialized.AddHandler((args) =>
{
    IReadOnlyCollection<IViewModelProperty> filterableProperties = args.ViewModel.GetFilterableProperties();
    if (filterableProperties.Any(p => p.Name == "CreatedBy") && filterableProperties.Any(p => p.Name == "UpdatedBy"))
    {
        args.ViewModel.NamedFilters.Add(new NamedFilter(
            "CreatedOrUpdatedByMe",
            UIResources.Core.CreatedOrUpdatedByMe,
            async () => new Filter(
                null,
                FilterLogicalOperator.Or,
                new Filter("CreatedBy", FilterOperator.Equal, ApplicationContext.UserIdentifier),
                new Filter("UpdatedBy", FilterOperator.Equal, ApplicationContext.UserIdentifier))
            ));
    }
});

In this example, a "Created or updated by me" named filter is added to all UI views that have a CreatedBy and UpdatedBy filterable properties.

Customize filter chip icons

When a filterable property is displayed as a chip in the filter bar, you can override the default data type icon with FilterIcon:

Properties.MyProperty.Filterable = true;
Properties.MyProperty.FilterIcon = Images.DarkTheme;

If FilterIcon is not set, the framework uses the default icon derived from the property data type.

This customization also exists for entity view parameters. See Use entity view parameters.

See also