This article introduces a free library that helps .NET developers to easily filter C# objects with object instances, to save them the effort of writing tedious filtering code.
Introduction
During implementation of a web application, searching for data according to certain criteria is a common requirement, that developers provide a searching panel to let business users provide some input values, then the input value gets sent to the server side, and the input values will be used to filter data in the database, at last the filter data will be sent back to web application and response of the search request.
This article introduces a free C# library that may save developers efforts of implementing such filtering logics.
Background
For simple searching use cases, the search logic is always simple and similar, yet for every search, developers have to repeat the tedious and brainless coding to fill it in.
Take a most simplified example of searching for books: Each Book
has a name property and a published year property.
public class Book
{
public string Name { get; set; } = null!;
public int PublishedYear { get; set; }
}
public class SearchForBookDto
{
public string? Name { get; set; }
public int? PublishedYear { get; set; }
}
To search for the book by either or both properties in the database, the code may be like the following:
IQueryable queryable = databaseContext.Set<Book>();
if (searchForBookDto.Name != null)
{
queryable = queryable.Where(book => book.Name == searchForBookDto.Name);
}
if (searchForBookDto.PublishedYear != null)
{
queryable = queryable.Where(book => book.PublishedYear == searchForBookDto.PublishedYear);
}
await queryable.ToListAsync();
It's easy to see in the use case that for each searching property, the logic is the same: if the property value is not null
, apply it to the queryable. This is quite tedious if there are a lot of such search fields to fill in for, or there are a lot of such searching features to implement.
Basic Use Case
Oasis.DynamicFilter
can help to simply the code to below:
var expressionMaker = new FilterBuilder().Register<Book, SearchForBookDto>().Build();
await databaseContext.Set<Book>()
.Where(expressionMaker.GetExpression<Book, SearchForBookDto>(searchForBookDto))
.ToListAsync();
In the two statements:
The first statement is the configuring process, to initialize a filter builder, then register the filtering on Book
with SearchForBookDto
, then it builds the FilterBuilder
instance into an instance of IFilter
interface, which developers can use to generate expressions or functions for filtering.
FilterBuilder
is a centralised class for developers to register all filtering pairs. Developers are expected to register all filtering cases with the same instance, and distribute the IFilter
interface instance it builds into all code that need the feature.
In the second statement, calling of expressionMaker.GetExpression<Book, SearchForBookDto>(searchForBookDto)
makes a Linq Expression
according to value of searchForBookDto
that filters Book
instances. Book
instances with Name
and PublishedYear
properties equal to properties of the same names from searchForBookDto
will be returned.
Support for Custom Filtering Configuration
To provide more flexibility to developers, Oasis.DynamicFilter
supports filtering entities with more complicated expressions, rather than only letting them visit direct properties. Check the following example:
public sealed class Book
{
public int PublishedYear { get; set; }
public string Name { get; set; } = null!;
public Author Author { get; set; } = null!;
}
public sealed class Author
{
public int BirthYear { get; set; }
public string Name { get; set; } = null!;
}
public sealed class AuthorFilter
{
public string? AuthorName { get; set; }
public int? Age { get; set; }
}
This time Book
has an navigation property named Author
, and to demonstrate the feature, the book searching use case becomes: search for all books that are written by author whose name contains string "John
", and are published before the author went 40
years old.
The following code generates the expression for this purpose:
var expressionMaker = new FilterBuilder()
.Configure<Book, AuthorFilter>()
.Filter(filter => book => book.Author.Name.Contains(filter.AuthorName!),
filter => !string.IsNullOrEmpty(filter.AuthorName))
.Filter(filter => book => book.PublishedYear - book.Author.BirthYear < filter.Age,
filter => filter.Age.HasValue)
.Finish()
.Build();
var filter = new AuthorFilter { Age = 40, AuthorName = "John" };
var exp = expressionMaker.GetExpression<Book, AuthorFilter>(filter);
The .Filter
method has 2 input parameters. The first parameter is the filtering method according to which Book
will be filtered; the second parameter is the condition to apply the filtering method. In the example above, if filter.AuthorName
is an empty string, the filtering method that author name of the book must contain the author name value will not be applied; if filter.Age
is null, the filtering method that the book must be pushlished before the author reaches a certain age won't be applied. The second parameter is null if not passed, in this case the filtering method will be applied no matter what value filter contains.
Allowing custom filtering configuration adds a lot usability to Oasis.DynamicFilter
. Combined with the basic filtering condition auto-generation feature, this library becomes a powerful centralized filtering condition registry which allows any filtering condition, and helps developers to save development efforts in coding trivial filtering rules.
Summary
For a quick demonstration of the use cases mentioned, please download and check the sample code attached. Oasis.DynamicFilter
is implemented in .NET standard 2.1, and the downloadable sample code is a Xunit test library in .NET 6. Sqlite is used in it to prove that it works well with Linq to SQL.
To find more details of the library, please visit its GitHub repository.
Should there be any inquiry or suggestion, please leave a comment here or submit a bug under the repository.
History
- 22nd October, 2023: Initial submission
- 28th October, 2023: Package version updated to 0.2.1
- 14th July, 2024: Package version updated to 0.3.0
- 27th July 2024: Pakcage version updated to 0.3.1