Blazor Components

Interactive UI components for building dynamic queries in Blazor applications, with optional MudBlazor integration.

Installation#

Install the three packages needed for a Blazor query builder via the .NET CLI.

shell
dotnet add package NetQueryBuilder
dotnet add package NetQueryBuilder.EntityFramework
dotnet add package NetQueryBuilder.Blazor

NetQueryBuilder provides the core query engine, NetQueryBuilder.EntityFramework adds EF Core integration for database-backed queries, and NetQueryBuilder.Blazor supplies the interactive UI components.

Setup#

Three steps to get the query builder running in your Blazor application.

1. Register services in Program.cs

Add your DbContext and the NetQueryBuilder query configurator to the service container.

csharp
// Register your DbContext
builder.Services.AddDbContext<YourDbContext>(options =>
    options.UseSqlServer(connectionString));

// Register the query configurator
builder.Services.AddScoped<IQueryConfigurator, EfQueryConfigurator<YourDbContext>>();

2. Add imports in _Imports.razor

Add the component namespace so Blazor can resolve the query builder tags.

razor
@using NetQueryBuilder.Blazor.Components

3. Include the stylesheet (optional)

The components ship with basic styling. Reference the bundled CSS in your index.html or App.razor.

html
<link href="_content/NetQueryBuilder.Blazor/css/netquerybuilder.css" rel="stylesheet" />

QueryBuilder Component#

The main component is QueryBuilder<TEntity>. Drop it into any Blazor page to get a complete query-building interface, including field selection, condition construction, execution, and results.

razor
@page "/products-query"
@using NetQueryBuilder.Blazor.Components
@using YourNamespace.Models
@inject IQueryConfigurator QueryConfigurator

<h3>Product Query Builder</h3>

<QueryBuilder TEntity="Product" />

@code {
    // The component injects the configurator automatically
    // and builds the query on initialization.
}

This renders a full query builder interface with four sections:

Handling Results#

Use the OnQueryExecuted callback to capture query results in your own code. The callback receives an IEnumerable<object> that you can cast to your entity type.

razor
<QueryBuilder TEntity="Product"
              OnQueryExecuted="HandleQueryResults" />

@code {
    private IEnumerable<Product> _queryResults;

    private void HandleQueryResults(IEnumerable<object> results)
    {
        _queryResults = results.Cast<Product>();
        // Process results -- export to CSV, update charts, etc.
    }
}

Expression Output#

The Expression parameter exposes the generated LINQ expression as a human-readable string. Use two-way binding with ExpressionChanged to keep a live copy in your page.

razor
<QueryBuilder TEntity="Product"
              Expression="@_currentExpression"
              ExpressionChanged="@HandleExpressionChanged" />

<pre>@_currentExpression</pre>

@code {
    private string _currentExpression;

    private void HandleExpressionChanged(string expression)
    {
        _currentExpression = expression;
        // Save the expression or use it elsewhere
    }
}

Individual Components#

For more granular control you can break the full QueryBuilder into its constituent parts and compose them yourself: ConditionComponent, PropertySelector, and QueryResultTable.

ConditionComponent

Renders the WHERE clause builder. Pass it the IQuery.Condition and wrap everything in a CascadingValue so child components can access the query context.

razor
@inject IQueryConfigurator QueryConfigurator

<CascadingValue Value="@_query">
    <ConditionComponent Condition="@_query.Condition" />
</CascadingValue>

<button class="btn btn-primary" @onclick="ExecuteQuery">Execute</button>

@code {
    private IQuery _query;

    protected override void OnInitialized()
    {
        _query = QueryConfigurator.BuildFor<Product>();
    }

    private async Task ExecuteQuery()
    {
        var results = await _query.Execute(50);
        // Process results
    }
}

PropertySelector

Renders the SELECT checkboxes. Bind SelectedProperties and SelectedPropertiesChanged to track which columns the user picks.

razor
<PropertySelector TEntity="Product"
                 SelectedProperties="@_selectedProperties"
                 SelectedPropertiesChanged="@HandleSelectedPropertiesChanged" />

@code {
    private List<SelectPropertyPath> _selectedProperties = new();

    private void HandleSelectedPropertiesChanged(List<SelectPropertyPath> properties)
    {
        _selectedProperties = properties;
        // Update your query or UI
    }
}

QueryResultTable

Renders the results grid. Feed it the data and the list of selected properties so it knows which columns to display.

razor
<QueryResultTable TEntity="Product"
                 Data="@_queryResults"
                 Properties="@_selectedProperties" />

Customization#

The components render standard HTML elements with CSS classes you can override. All class names are prefixed with nqb- to avoid conflicts with your application styles.

CSS class overrides

css
/* Main query builder container */
.nqb-query-builder {
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    padding: 1.5rem;
}

/* Individual condition rows */
.nqb-condition {
    background: #fafafa;
    margin-bottom: 0.5rem;
}

/* Results table */
.nqb-results-table {
    width: 100%;
    border-collapse: collapse;
}

Integration with MudBlazor

Note

MudBlazor is entirely optional. The query builder components work standalone with their own bundled CSS. MudBlazor is used only in the sample application for enhanced styling.

Wrap the query builder in MudBlazor layout components for a consistent look.

razor
<MudCard>
    <MudCardContent>
        <QueryBuilder TEntity="Product" />
    </MudCardContent>
</MudCard>

Integration with Bootstrap

The same approach works with Bootstrap cards or any other CSS framework.

razor
<div class="card">
    <div class="card-body">
        <QueryBuilder TEntity="Product" />
    </div>
</div>

Custom Operators#

You can register custom filter operators via ConfigureConditions during service registration. The Blazor components will automatically pick up any registered operators and display them in the condition dropdown.

csharp
builder.Services.AddScoped<IQueryConfigurator>(provider =>
{
    var configurator = new EfQueryConfigurator<YourDbContext>(
        provider.GetRequiredService<YourDbContext>());

    configurator.ConfigureConditions(config =>
    {
        config.RegisterOperator<YourCustomOperator>();
    });

    return configurator;
});

Once registered, the custom operator appears alongside the built-in operators (Equals, Contains, Greater Than, etc.) in every ConditionComponent dropdown.

Performance#

When working with large data sets, keep these tips in mind to ensure a responsive UI.

Component Lifecycle#

The QueryBuilder component subscribes to internal events and creates timers for debouncing. To prevent memory leaks, it implements IAsyncDisposable. When you build custom components on top of the query builder, follow the same pattern.

csharp
public partial class MyQueryComponent : ComponentBase, IAsyncDisposable
{
    private System.Threading.Timer? _debounceTimer;
    private IQuery _query = null!;

    protected override void OnInitialized()
    {
        _query = QueryConfigurator.BuildFor<Product>();
        _query.Condition.ConditionChanged += OnConditionChanged;
    }

    private void OnConditionChanged(object? sender, EventArgs e)
    {
        // Debounce the recompilation
        _debounceTimer?.Dispose();
        _debounceTimer = new System.Threading.Timer(_ =>
        {
            InvokeAsync(() => { StateHasChanged(); });
        }, null, 300, Timeout.Infinite);
    }

    public async ValueTask DisposeAsync()
    {
        _query.Condition.ConditionChanged -= OnConditionChanged;
        if (_debounceTimer is not null)
        {
            await _debounceTimer.DisposeAsync();
        }
    }
}

Key points: