Stackworx.Hotchocolate.MuiDataGrid builds filter/sort expressions from MUI DataGrid model payloads.
This project is licensed under the MIT License. See LICENSE.
GitHub Actions runs formatting, build, and test checks on pull requests and pushes to main.
NuGet publishing is a local operation.
To publish packages to NuGet.org locally:
VERSION=2.0.0 NUGET_API_KEY=your_key_here ./scripts/release.sh- Add a reference to the library.
<ItemGroup>
<ProjectReference Include="src/Hotchocolate.MuiDataGrid/Hotchocolate.MuiDataGrid.csproj" />
</ItemGroup>- Register the MUI scalar/enum types with Hot Chocolate.
builder
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMuiDataGrid();This repository targets net10.0.
This release contains a breaking API change: ExpressionBuilder<T> now requires a DataType<T> instead of a BaseColumnLookup<T>, and custom handlers are configured on the data type rather than registered later on the builder.
new ExpressionBuilder<T>(new PersonColumnLookup())→new ExpressionBuilder<T>(new PersonDataType())BaseColumnLookup<T>mappings move intoDataType<T>.Configure(...)builder.AddHandler(...)is removed; use.SetHandler(...)on the mapped property instead- field names still default to camelCase property names, and
SetName(...)is available to preserve old field contracts
public sealed class PersonColumnLookup : BaseColumnLookup<Person>
{
protected override ColumnLookupMember? InternalLookup(string column)
{
return column switch
{
"firstname" => this.GetMemberExpression(p => p.Firstname),
"apartmentType" => this.GetMemberExpression(p => p.Address!.Apartment.ApartmentType),
_ => null,
};
}
}
var builder = new ExpressionBuilder<Person>(new PersonColumnLookup());
builder.AddHandler("apartmentType", new DefaultEnumSingleSelectHandler<Person, ApartmentType>());public sealed class PersonDataType : DataType<Person>
{
protected override void Configure(DataTypeBuilder<Person> builder)
{
builder.Property(p => p.Firstname);
builder.Property(p => p.Address!.Apartment.ApartmentType)
.SetName("apartmentType")
.SetHandler(new DefaultEnumSingleSelectHandler<Person, ApartmentType>());
}
}
var builder = new ExpressionBuilder<Person>(new PersonDataType());- Replace each
BaseColumnLookup<T>with aDataType<T>. - Move each
switchcase intobuilder.Property(...)calls. - Add
SetName(...)where the old field name does not match the inferred property name. - Move each
builder.AddHandler(...)call into the corresponding property configuration viaSetHandler(...). - Update every
ExpressionBuilder<T>construction site to pass the new data type.
An adapter is available in src/Hotchocolate.MudDataGrid to translate MudBlazor DataGrid state into the MUI filter/sort contract used by ExpressionBuilder<T>.
Register it alongside AddMuiDataGrid():
builder
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMuiDataGrid()
.AddMudDataGridAdapter();For full details (input shape, operator mapping, resolver example), see src/Hotchocolate.MudDataGrid/README.md.
Run the integration tests from the repo root:
./scripts/tests.shOr run the test project directly:
dotnet test ./tests/Hotchocolate.MuiDataGrid.Test --configuration ReleaseThe backend accepts MuiDataGridFilterInput and sort items from your GraphQL query, then applies them with ExpressionBuilder<T>.
Map MUI field names (for example firstname) to entity members with DataType<T>. By default, fields are inferred from property names using camelCase, and you can override names with SetName(...) during transitions.
using Stackworx.Hotchocolate.MuiDataGrid;
public sealed class PersonDataType : DataType<Person>
{
protected override void Configure(DataTypeBuilder<Person> builder)
{
builder.Property(p => p.Id);
builder.Property(p => p.Firstname);
builder.Property(p => p.Lastname);
builder.Property(p => p.Age);
builder.Property(p => p.CreatedAtDate);
builder.Property(p => p.Address!.Apartment.ApartmentType)
.SetName("apartmentType")
.SetHandler(new DefaultEnumSingleSelectHandler<Person, ApartmentType>());
}
}Create one ExpressionBuilder<T> for the resolver from your configured DataType<T> and apply filter/sort.
using Microsoft.EntityFrameworkCore;
using Stackworx.Hotchocolate.MuiDataGrid;
public class Query
{
public async Task<List<Person>> People(
MuiDataGridFilterInput? filters,
IList<MuiDataGridSortItem>? sorting,
AppDbContext dbContext)
{
var builder = new ExpressionBuilder<Person>(new PersonDataType());
IQueryable<Person> query = dbContext.People;
if (filters is not null)
{
query = query.Where(builder.Filter(filters));
}
if (sorting is not null)
{
query = builder.Sort(query, sorting);
}
return await query.ToListAsync();
}
}query People($filters: MuiDataGridFilterInput, $sorting: [MuiDataGridSortItemInput!]) {
people(filters: $filters, sorting: $sorting) {
id
firstname
lastname
}
}{
"filters": {
"logicOperator": "and",
"items": [
{ "field": "firstname", "operator": "contains", "value": "ann" },
{ "field": "age", "operator": ">=", "value": 18 }
]
},
"sorting": [
{ "field": "createdAtDate", "sort": "desc" }
]
}Not all MUI operators are supported for all default handlers.
equals, contains, startsWith, endsWith, isEmpty, isNotEmpty, isAnyOf
=, !=, >, >=, <, <=, isEmpty, isNotEmpty, isAnyOf
is
is, not, after, onOrAfter, before, onOrBefore, isEmpty, isNotEmpty
getGridSingleSelectOperators().filter(({ value }) =>
['equals', 'isEmpty', 'isNotEmpty', 'isAnyOf'].includes(value)
);isAnyOf — matches rows where the entity's enum collection contains any of the filter values.
Register explicitly on the property; auto-detection is not performed:
// Configure once for IEnumerable<TEnum>; this also covers IList<TEnum> and ICollection<TEnum>
builder.Property(u => u.Roles).SetEnumMultiSelectHandler();Filter values are enum name strings normalised via Humanizer, e.g. SUPER_USER, Super User, and SuperUser all resolve to the same member. Unknown values throw ArgumentException immediately.