ASP.NET Core
Server-side query building with Tag Helpers, View Components, and session-based state management. No JavaScript required.
Installation#
Install the ASP.NET Core integration package via the .NET CLI.
dotnet add package NetQueryBuilder.AspNetCore
The NetQueryBuilder.AspNetCore package includes NetQueryBuilder and NetQueryBuilder.EntityFramework as dependencies, so a single install command is all you need.
Setup#
Setting up NetQueryBuilder in an ASP.NET Core application requires four steps: registering services, configuring middleware, adding Tag Helpers, and including the stylesheet.
1. Register services in Program.cs
Use the AddNetQueryBuilder<TContext> extension method to register all required services at once.
This registers the options as a singleton, IQuerySessionService as a singleton, and IQueryConfigurator
as scoped to match the DbContext lifetime.
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddDbContext<YourDbContext>(options => options.UseSqlServer(connectionString)); // Single line registers all NetQueryBuilder services builder.Services.AddNetQueryBuilder<YourDbContext>(options => { options.SessionTimeout = TimeSpan.FromMinutes(30); options.DefaultPageSize = 10; });
2. Configure middleware
Call UseNetQueryBuilder() after UseRouting() and before MapRazorPages().
This configures session state and static file serving for the built-in CSS assets.
var app = builder.Build(); app.UseRouting(); app.UseNetQueryBuilder(); // Handles session + static files app.MapRazorPages(); app.Run();
3. Add Tag Helpers in _ViewImports.cshtml
Register the Tag Helpers so Razor Pages can recognize the nqb- elements.
@addTagHelper *, NetQueryBuilder.AspNetCore
4. Include the stylesheet
Add the default NetQueryBuilder stylesheet to your layout or page.
<link rel="stylesheet" href="~/_content/NetQueryBuilder.AspNetCore/css/netquerybuilder.css" />
The ASP.NET Core integration uses standard HTML forms and server-side rendering. All interactions happen through regular HTTP POST requests handled by Razor Pages, so there are no JavaScript dependencies to manage.
Page Model#
Create a Razor Page that inherits from NetQueryPageModelBase. The base class provides
session management, property binding, and automatic handler dispatch using reflection so you do not need
entity-type switch statements.
QueryBuilder.cshtml.cs
using NetQueryBuilder.AspNetCore.Pages; using NetQueryBuilder.AspNetCore.Services; using NetQueryBuilder.Configurations; public class QueryBuilderModel : NetQueryPageModelBase { public QueryBuilderModel( IQuerySessionService sessionService, IQueryConfigurator configurator) : base(sessionService, configurator) { } public void OnGet() { } // Base class automatically handles: // - OnPostChangeEntity() // - OnPostToggleProperty() // - OnPostUpdateProperties() // - OnPostAddCondition() // - OnPostRemoveCondition() // - OnPostAddGroup() // - OnPostExecuteQueryAsync() // - OnPostChangePageAsync() // - OnPostClearQuery() }
QueryBuilder.cshtml
@page @model QueryBuilderModel <h1>Query Builder</h1> <form method="post"> <nqb-entity-selector session-id="@Model.SessionId"></nqb-entity-selector> <nqb-property-selector session-id="@Model.SessionId"></nqb-property-selector> <nqb-condition-builder session-id="@Model.SessionId"></nqb-condition-builder> @await Component.InvokeAsync("ExpressionDisplay", new { sessionId = Model.SessionId }) <button type="submit" asp-page-handler="ExecuteQuery" class="nqb-button nqb-button-primary"> Run Query </button> </form> @if (Model.State.Results != null) { @await Component.InvokeAsync("QueryResults", new { sessionId = Model.SessionId }) @await Component.InvokeAsync("Pagination", new { sessionId = Model.SessionId }) }
The base class uses reflection to dispatch query execution to the correct generic method based on the selected entity type. This eliminates if-else chains and lets you support any number of entity types without modifying the page model.
Tag Helpers#
NetQueryBuilder provides three Tag Helpers that render server-side HTML form controls.
All Tag Helpers use the nqb- prefix for namespace isolation and require a session-id
attribute to retrieve session state.
Entity Selector
Renders a dropdown allowing the user to choose which entity type to query. Submitting the form triggers the OnPostChangeEntity handler.
<nqb-entity-selector session-id="@Model.SessionId"></nqb-entity-selector>
| Attribute | Type | Description |
|---|---|---|
session-id |
string |
The session identifier used to retrieve available entity types from the configurator. |
Property Selector
Renders checkboxes for selecting which entity properties to include in query results. Each checkbox triggers the OnPostToggleProperty handler, or use batch mode with OnPostUpdateProperties.
<nqb-property-selector session-id="@Model.SessionId"></nqb-property-selector>
| Attribute | Type | Description |
|---|---|---|
session-id |
string |
The session identifier used to retrieve available properties for the selected entity. |
Condition Builder
Renders the full condition building UI with property dropdowns, operator selectors, and value inputs. Supports adding, removing, and grouping conditions through standard form posts.
<nqb-condition-builder session-id="@Model.SessionId"></nqb-condition-builder>
| Attribute | Type | Description |
|---|---|---|
session-id |
string |
The session identifier used to retrieve the current query and its conditions. |
View Components#
View Components handle the display of query results and related UI. They are invoked with Component.InvokeAsync in Razor views.
QueryResults
Displays query results in a responsive HTML table. Columns are determined by the selected properties. Renders nothing when no results are available.
@await Component.InvokeAsync("QueryResults", new { sessionId = Model.SessionId })
Pagination
Renders page navigation controls. Links submit to the OnPostChangePageAsync handler on the base page model. Hidden when the result set fits on a single page.
@await Component.InvokeAsync("Pagination", new { sessionId = Model.SessionId })
ExpressionDisplay
Shows the generated LINQ expression tree as a human-readable string. Useful during development and debugging to verify the query being built.
@await Component.InvokeAsync("ExpressionDisplay", new { sessionId = Model.SessionId })
Session Management#
The IQuerySessionService manages query state between HTTP requests using a thread-safe
ConcurrentDictionary. Each user gets a unique session ID stored in an HTTP session cookie.
// Get the current session state var state = sessionService.GetState(sessionId); // Access the current query var query = state.Query; // Access stored results var results = state.Results; // Create or refresh a query for an entity type var query = sessionService.GetOrCreateQuery(sessionId, entityType, configurator);
Session state tracks LastAccessTime using DateTime.UtcNow on every access.
A background SessionCleanupService periodically removes expired sessions based on
the configured timeout interval.
The session service always creates a new IQuery instance with a fresh DbContext
on every request, transferring conditions from the previous query. This avoids
ObjectDisposedException errors that would occur if the disposed context from a prior
request were reused.
Custom Handlers#
You can add custom POST handlers alongside the base functionality. The base class exposes
SessionService and Configurator as protected fields for use in derived classes.
public class QueryBuilderModel : NetQueryPageModelBase { public QueryBuilderModel( IQuerySessionService sessionService, IQueryConfigurator configurator) : base(sessionService, configurator) { } public void OnGet() { } // Custom handler for exporting results to CSV public async Task<IActionResult> OnPostExportAsync() { var state = SessionService.GetState(SessionId); if (state.Results == null) return RedirectToPage(); var csv = GenerateCsv(state.Results); return File( Encoding.UTF8.GetBytes(csv), "text/csv", "export.csv"); } }
Add the corresponding button in the Razor view.
<button type="submit" asp-page-handler="Export" class="nqb-button"> Export to CSV </button>
Configuration Options#
Configure NetQueryBuilder behavior through the NetQueryBuilderOptions class
passed to AddNetQueryBuilder<TContext>.
| Property | Type | Default | Description |
|---|---|---|---|
SessionTimeout |
TimeSpan |
30 minutes | Duration before an idle session expires and is eligible for cleanup. |
DefaultPageSize |
int |
10 | Number of results displayed per page by default. |
MaxPageSize |
int |
100 | Maximum allowed page size to prevent excessively large result sets. |
UseSessionStorage |
bool |
true |
Whether to enable session-based state storage for query persistence across requests. |
SessionCleanupInterval |
TimeSpan |
5 minutes | Interval between background cleanup runs. Set to TimeSpan.Zero to disable. |
EnableSessionCleanup |
bool |
true |
Whether to run the background SessionCleanupService that removes expired sessions. |
builder.Services.AddNetQueryBuilder<AppDbContext>(options => { options.SessionTimeout = TimeSpan.FromHours(1); options.DefaultPageSize = 25; options.MaxPageSize = 200; options.SessionCleanupInterval = TimeSpan.FromMinutes(10); });
Styling#
The included stylesheet provides a clean, accessible design. All CSS classes are prefixed with nqb-
for namespace isolation. Customize the appearance by overriding CSS custom properties.
CSS variables
/* Override in your own stylesheet */ :root { --nqb-primary-color: #1E88E5; --nqb-border-radius: 4px; --nqb-spacing: 1rem; }
Custom stylesheet override
Add your own stylesheet after the default one to override specific styles without modifying the library CSS.
<!-- Default NetQueryBuilder styles --> <link rel="stylesheet" href="~/_content/NetQueryBuilder.AspNetCore/css/netquerybuilder.css" /> <!-- Your custom overrides --> <link rel="stylesheet" href="~/css/custom-query-builder.css" />
Accessibility#
All rendered HTML follows WAI-ARIA best practices. The Tag Helpers generate semantic markup with proper accessibility attributes built in.
- Label associations — Every form control has a properly associated
<label>element usingfor/idpairing. - ARIA attributes — Condition groups use
role="group"andaria-labelfor screen readers. Error messages usearia-describedbyto link to their associated inputs. - Keyboard navigation — All interactive controls are reachable via keyboard. Tab order follows the visual layout. Buttons and links have visible focus indicators.
- Semantic HTML — The generated markup uses appropriate elements (
<select>,<fieldset>,<table>) rather than generic<div>elements, giving assistive technologies the context they need.