Skip to main content Skip to footer

Add AI-Powered Natural Language Filtering to a Blazor DataGrid

Quick Start Guide
What You Will Need

Visual Studio 2026

ComponentOne Blazor Edition

Controls Referenced

FlexGrid for Blazor

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.

Ready to get started? Download ComponentOne Today!

AI Filter Blazor FlexGrid Order

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.

AI Filter Diagram

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:

AI Filter Blazor FlexGrid

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

AI Filter Blazor FlexGrid Nary

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.

AI Filter Blazor FlexGrid Date

Ready to try it out? Download ComponentOne Today!

comments powered by Disqus