Introduction
A generic composite filter functionality to handle the client-side complex API filter queries is always a necessary component in API development. In this article, I’m going to explain the generic composite filter which I have developed for the ASP.NET Core web API application and published on GitHub.
Composite Filter
If you check the Utility folder from the GitHub repository, you will find two cs files.
- CompositeFilter.cs
- RootFilter.cs
RootFilter.cs
It is a model class that we used to deserialize the filter query coming from the client.
private static Expression public class RootFilter
{
public List<Filter> Filters { get; set; }
public string Logic { get; set; }
}
public class Filter
{
public string Field { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
public string Logic { get; set; } // This is the nested "logic" property for the inner filters array
public List<Filter> Filters { get; set; } // Nested filters array
}
Assume we have the following filter query for the API from the client.
https://localhost:44360/api/values?filter={"filters":[{"field":"Name","operator":"contains","value":"0"},{"operator":"contains","value":"0","field":"description"}],"logic":"and"}
The above RootFilter model will be used to deserialize the filter query by using the Newtonsoft.Json library.
string filter = HttpContext.Request.Query["filter"];
if (!string.IsNullOrEmpty(filter))
{
filterResult = JsonConvert.DeserializeObject<RootFilter>(filter);
}
The above code will deserialize the filter query from the client request.
CompositeFilter.cs
The composite filter file consists of four methods.
1. BuildFilterExpression
private static Expression BuildFilterExpression(Filter filter, ParameterExpression parameter)
{
if (filter.Filters != null && filter.Filters.Any())
{
if (filter.Logic?.ToLower() == "and")
{
var andFilters = filter.Filters.Select(f => BuildFilterExpression(f, parameter));
return andFilters.Aggregate(Expression.AndAlso);
}
else if (filter.Logic?.ToLower() == "or")
{
var orFilters = filter.Filters.Select(f => BuildFilterExpression(f, parameter));
return orFilters.Aggregate(Expression.OrElse);
}
}
if (filter.Value == null || string.IsNullOrWhiteSpace(filter.Value.ToString()))
return null;
var property = Expression.Property(parameter, filter.Field);
var constant = Expression.Constant(filter.Value);
switch (filter.Operator.ToLower())
{
case "eq":
return Expression.Equal(property, constant);
case "neq":
return Expression.NotEqual(property, constant);
case "lt":
return Expression.LessThan(property, constant);
case "lte":
return Expression.LessThanOrEqual(property, constant);
case "gt":
return Expression.GreaterThan(property, constant);
case "gte":
return Expression.GreaterThanOrEqual(property, constant);
case "contains":
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
return Expression.Call(property, containsMethod, constant);
case "startswith":
var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string), typeof(StringComparison) });
// Convert the constant value to lowercase for case-insensitive comparison
var constantLower = Expression.Call(constant, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
return Expression.Call(property, startsWithMethod, constantLower, Expression.Constant(StringComparison.OrdinalIgnoreCase));
// Add more operators as needed...
default:
throw new ArgumentException($"Unsupported operator: {filter.Operator}");
}
}
This function will build the fundamental filter expression with different operators like eq, neq,lt, lte, gt, gte, contains, and startswith.



Rohan NegiPosted Oct 15, 2024, 9:58 AM
Will this work for this URL:- https://localhost:44360/api/values?filter={"filters":[{"field":"Name","operator":"contains","value":"0"},{"operator":"contains","value":"0","field":"description"}, {"operator":"contains","value":"0","field":"description"}],"logic":"and"}.
Tomas AxellPosted Apr 19, 2024, 12:42 PM
Great article, how can we handle null for incoming filter?System.ArgumentNullException: Value cannot be null. (Parameter 'left')
ajay jangamPosted Sep 4, 2023, 1:47 PM
Nice Article , but how can we filter it on integer field , like product id equal to 10?