Add AI-Powered Natural Language Filtering to a Blazor DataGrid
| Quick Start Guide | |
|---|---|
| What You Will Need |
Visual Studio 2026 |
| Controls Referenced | |
| Tutorial Concept | Discover how to use AI to translate natural-language queries into FlexGrid filters using Blazor. |
Modern app users expect to search and filter data using natural language rather than complex filter dialogs. In previous articles, we showed how to add AI-powered filtering to FlexGrid for WinForms and how to enable traditional filtering in FlexGrid for Blazor. In this article, we'll bring those ideas together by enabling Blazor users to filter a FlexGrid using natural-language queries.
Although FlexGrid for Blazor doesn't provide the same filter-definition API as its WinForms counterpart, it does support the serialization and deserialization of filter expressions. By leveraging this feature, we can use AI to generate valid filter definitions from natural language requests and apply them directly to the grid. The result is an intuitive, AI-powered filtering experience that requires no custom query language or advanced user knowledge.
Follow along below to learn how AI-powered filtering in FlexGrid for Blazor works, or jump to the end to download a complete sample.
- How the AI-Powered Filtering Works
- Part 1: Setting Up the UI
- Part 2: Building the AI Prompt
- Part 3: Calling the Generative AI Service
- Part 4: Deserializing the JSON Response and Applying the Filter
- Download the Complete Sample
Ready to get started? Download ComponentOne Today!

How the AI-Powered Filtering Works
To enable AI-powered filtering, we can leverage generative AI services such as OpenAI, Azure OpenAI, or Google Gemini. These services are excellent at interpreting natural language and returning structured data in a predictable format. Rather than generating C# code, we'll have the AI generate filter definitions in a format that FlexGrid can understand and apply directly at runtime.
This is where FlexGrid's filter serialization feature becomes valuable. FlexGrid for Blazor can represent filter definitions as JSON, allowing filter state to be saved and later restored. While serialization is commonly used to persist user settings across application sessions, we can also use it to bridge AI and the grid.
In our solution, the AI converts a user's natural language request into a JSON filter definition. The application then deserializes that JSON into a FlexGrid filter expression and applies it to the grid. Because both the AI and FlexGrid use the same JSON representation, we can create a powerful natural-language filtering experience without generating or compiling code at runtime.

Note that we won't need to pass the actual data to the AI service; we will just explain what the data set looks like. That's part of what the AI model needs to understand how the data filter can be generated. The other important part is the instruction—more on that in Part 2.
For this sample, we are using Google Gemini as the generative AI service. It's very quick and easy to create free keys for testing. You can use any service you want, and the same prompts should work, though you will want to test it out.
Why Use AI-Powered Filtering?
Some users prefer natural-language filtering, like a Google search box, on their data set. Where it really shines compared to traditional filter menus is when you want to combine two conditions. But like anything in software development, there are tradeoffs to consider. The biggest drawback is that users are more likely to face a glitch in their query results since it's more difficult to test every possible search scenario.
Part 1: Setting Up the UI
The minimum UI we need for this sample is a textbox to collect the user's query, a button, and the FlexGrid Blazor datagrid to display the dataset.
Enter Gemini API key: <input type="text" class="form-control" @bind="@ApiKey" />
Enter filter query: <input type="text" class="form-control" @bind="@UserQuery" />
<button @onclick="OnButtonClick">Apply Filter Query</button>
<p>Output: @outputResponse </p>
<FlexGrid @ref="grid" AutoGenerateColumns="false" ItemsSource="_customers">
<FlexGridColumns>
<GridColumn Header="Active" Binding="Active" AllowFiltering="false" />
<GridColumn Header="First Name" Binding="FirstName" />
<GridColumn Header="Last Name" Binding="LastName" />
<GridColumn Header="Country" Binding="CountryId" DataMap="@_countryDataMap" />
<GridColumn Header="City" Binding="City" />
<GridColumn Header="OrderCount" Binding="OrderCount" />
<GridColumn Header="LastOrderDate" Binding="LastOrderDate" />
</FlexGridColumns>
</FlexGrid>
For the complete razor markup and code, download the sample application.
Part 2: Building the AI Prompt
The heavy lifting in this sample occurs during the construction of the AI prompt. The prompt serves as a specification that teaches the AI to convert a natural-language query into a valid JSON filter definition. It describes the available fields, supported filter operations, and required JSON structure so that the generated output can be deserialized directly into a FlexGrid filter expression and applied at runtime.
The key ingredients the AI prompt must include:
- User's query - the natural-language query entered by the user
- Data schema - the available fields and data types in our data set
- Instructions - i.e., "You are an AI that converts natural language queries into a JSON representation of a C1.DataCollection.FilterExpression..."
- Examples - they help produce the best and most consistent results
- Limitations - or other notes for the AI model to avoid, you'll probably build upon this as you test and find things that don't work
Here's the full prompt that we built for the sample:
private static string BuildFullAIPrompt(string query)
{
string aiPrompt = $$"""
You are an AI that converts natural language queries into a JSON representation of a C1.DataCollection.FilterExpression.
Available customer fields:
- FirstName (string)
- LastName (string)
- Active (boolean)
- City (string)
- Country (string)
- OrderCount (number)
- LastOrderDate (date)
Generate exactly one filter expression for the given User Query:
{{query}}
Text Filters
Use this format:
{
"Kind": "Text",
"FilterPath": "FieldName",
"FilterOperation": "Contains",
"Value": "text",
"MatchCase": false,
"MatchWholeWord": false
}
Allowed text operations:
- Contains
- Equals
- StartsWith
- EndsWith
Boolean Filters
Use this format:
{
"Kind": "Operation",
"FilterPath": "Active",
"FilterOperation": "Equals",
"Value": true
}
Numeric Filters
Use this format:
{
"Kind": "Operation",
"FilterPath": "OrderCount",
"FilterOperation": "GreaterThan",
"Value": 1
}
Allowed numeric operations:
- Equals
- NotEquals
- GreaterThan
- GreaterThanOrEqual
- LessThan
- LessThanOrEqual
Date Filters
Use this format:
{
"Kind": "Operation",
"FilterPath": "LastOrderDate",
"FilterOperation": "GreaterThan",
"Value": "2025-01-01"
}
Rules:
- Date Values must be in the format YYYY-MM-DD.
- Convert common expressions such as "5 days ago" into a properly formatted date using the current date as starting point
Examples:
- If today is 2026-08-01 and the query requests "before 10 days ago" the resulting Value is 2026-07-21, and FilterOperation is "LessThan"
- If today is 2026-08-01 and the query requests "the past year" the resulting Value is 2025-08-01, and FilterOperation is "GreaterThan"
Combination Filters
To combine and nest multiple filters with AND/OR logic, use this format with "Nary" as the "Kind" of filter, and children inside an "Expressions" array:
{
"Kind": "Nary",
"FilterCombination": "And",
"Expressions": [
{
"Kind": "Operation",
"FilterPath": "City",
"FilterOperation": "Contains",
"Value": "a"
},
{
"Kind": "Nary",
"FilterCombination": "Or",
"Expressions": [
{
"Kind": "Operation",
"FilterPath": "City",
"FilterOperation": "Contains",
"Value": "be"
},
{
"Kind": "Nary",
"FilterCombination": "And",
"Expressions": [
{
"Kind": "Operation",
"FilterPath": "City",
"FilterOperation": "Contains",
"Value": "sou"
},
{
"Kind": "Nary",
"FilterCombination": "And",
"Expressions": [
{
"Kind": "Operation",
"FilterPath": "City",
"FilterOperation": "NotEqualText",
"Value": "h"
},
{
"Kind": "Operation",
"FilterPath": "City",
"FilterOperation": "StartsWith",
"Value": "r"
}
]
}
]
}
]
}
]
}
Rules
- Do not generate a wrapper object such as FilterExpression.
- Return valid JSON only.
Examples
Input:
Show employees who have ordered more than once
Output:
{
"Kind": "Operation",
"FilterPath": "OrderCount",
"FilterOperation": "GreaterThan",
"Value": 1
}
Input:
Show active employees
Output:
{
"Kind": "Operation",
"FilterPath": "Active",
"FilterOperation": "Equals",
"Value": true
}
Input:
Show employees in Pittsburgh
Output:
{
"Kind": "Text",
"FilterPath": "City",
"FilterOperation": "Equals",
"Value": "Pittsburgh",
"MatchCase": false,
"MatchWholeWord": false
}
""";
return aiPrompt;
}
This prompt includes instructions for formatting the JSON filter expression object based on C1.DataCollection.FilterExpression. The filter expression's Kind can include:
- Text: string-based, text matching filters
- Operator: numerical/date filters greater/less than
- Binary: to create a left and right filter combination (note: not used in this prompt)
- Nary: to combine more than one filter
Example of a simple Text filter:

Example of a combination Nary (more than one) filter:

Part 3: Calling the Generative AI Service
For this sample, we're using Google Gemini by calling this endpoint with our free API key.
https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key={key}
Depending on when you're reading this, they may change how to access the latest model. You can find more available Gemini models by calling this URL:
https://generativelanguage.googleapis.com/v1beta/models?key={key}
The sample uses a standard HttpClient to create the request and response. Due to the clearly written prompt from the previous step, we can expect the response to be a clean JSON-formatted filter expression such as this one:
Output example:
{
"Kind": "Operation",
"FilterPath": "OrderCount",
"FilterOperation": "GreaterThan",
"Value": 50
}
The GetAIResponse method accepts the full AI prompt and our API key, and it returns the JSON response as a string.
var aiResponse = await GetAIResponse(BuildFullAIPrompt(UserQuery), ApiKey);
...
public async static Task<string> GetAIResponse(string prompt, string key)
{
var url = $"https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key=" + key;
var requestBody = new
{
contents = new[]
{
new {
parts = new[] {
new { text = prompt }
}
}
}
};
HttpClient client = new();
try
{
string jsonPayLoad = JsonSerializer.Serialize(requestBody);
var content = new StringContent(jsonPayLoad, System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
var responseString = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(responseString);
var responseText = doc.RootElement
.GetProperty("candidates")[0]
.GetProperty("content")
.GetProperty("parts")[0]
.GetProperty("text")
.GetString();
return responseText;
}
else
{
string errorDetails = await response.Content.ReadAsStringAsync();
return response.StatusCode.ToString() + " " + response.ReasonPhrase + " " + errorDetails;
}
}
catch (Exception ex)
{
return "Error: " + ex.Message;
}
}
Part 4: Deserializing the JSON Response and Applying the Filter
The final step is to deserialize the JSON response into a FilterExpression object and apply it to the FlexGrid.
System.Text.Json includes the essential JSON deserialization classes, but we also need a converter class for the FilterExpression object. The C1.DataColleciton.Serialization library includes this necessary converter (FilterExpressionJsonConverter), so this code can be quite clean and simple.
Once we have a deserialized FilterExpression object, we can simply apply this filter to the FlexGrid through its DataCollection.FilterAsync method.
try
{
var filter = DeserializeFilterExpression(aiResponse);
await grid.DataCollection.FilterAsync(filter);
outputResponse = aiResponse;
}
catch
{
outputResponse = aiResponse;
}
...
public static FilterExpression DeserializeFilterExpression(string json)
{
var options = new JsonSerializerOptions();
options.Converters.Add(new FilterExpressionJsonConverter());
return JsonSerializer.Deserialize<FilterExpression>(
json,
options)!;
}
Download the Complete Sample
The code above demonstrates the key steps and snippets of code that build the AI-powered Blazor filtering. You can download the complete sample here.
Trouble with Date Queries
Note that this sample should be viewed as a starting point that demonstrates how the feature can be implemented. You'll want to test additional scenarios for your users and data, and modify the AI prompt to optimize results.
For instance, a known limitation of the free version of the Gemini AI model is that it does not know the current date. If you query "show customers who ordered within the past year," it will result in an incorrect date range.
You can work around this with a free AI model by including the current date in the query. If you are implementing this service in a real-world scenario, you will likely be using a better AI service, and this should not be a problem.

Ready to try it out? Download ComponentOne Today!