blazormudblazorpagedlist

Use MudBlazor MudDataGrid ServerData to load paged data from an API


In my opinion the documentation for MudDataGrid is a bit lacking when it comes to loading paged data from a dynamic source such as an API.


Solution

  • Let's say you want to make a blazor page that has a list of animals on it, displayed in a MudDataGrid. The animal data will come from an API.

    How do I use the ServerData attribute?

    Firstly, define DTOs to handle the user's request, and the response from the API:

    public class GridDataRequestDto
    {
        public int Page { get; set; } = 0; // The page number for the data we're requesting
        public int PageSize { get; set; } = 10; // The number of items per page
    }
    
    public class AnimalListDto
    {
        public List<AnimalListItemDto> Items { get; set; } = new();
        public int ItemTotalCount { get; set; } = 0; // The total count of items before paging
    }
    
    public class AnimalListItemDto
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
    

    This, then, is the implementation of your MudDataGrid component in your blazor page:

    <MudDataGrid ServerData="LoadGridData"
       T="AnimalListItemDto" @ref="_dataGrid">
        <Columns>
            <PropertyColumn Property="@(item => item.Name)" Title="Animal" />
         </Columns>
        <PagerContent>
            <MudDataGridPager T="AnimalListItemDto" />
        </PagerContent>
    </MudDataGrid>
    

    As you can see, the Grid's ServerData attribute is set to call a method called LoadGridData. We define that in our blazor page code:

    private MudDataGrid<AnimalListItemDto>? _dataGrid;
    private GridDataRequestDto _requestDto = new();
    
    private async Task<GridData<AnimalListItemDto>> LoadGridData(GridState<AnimalListItemDto> state)
    {
        _requestDto.Page = state.Page;
        _requestDto.PageSize = state.PageSize;
    
        AnimalListDto apiResponse = await GetAnimalList(_requestDto);
        GridData<AnimalListItemDto> data = new()
            {
                Items = apiResponse.Items,
                TotalItems = apiResponse.ItemTotalCount
            };
    
        return data;
    }
    

    You need to create the GetAnimalList() method that performs the API call, so on the server you would do your DB query and return a AnimalListDto result with the Items and ItemTotalCount properties populated.

    After you've done that, congratulations! You have successfully implemented ServerData in MudDataGrid.

    How do I handle row clicks?

    Say you want the user to view an animal when they click on a MudDataGrid row. Also you want to put a button on each row to allow the user to edit the animal if they click it.

    Let's modify the MudDataGrid implementation in the blazor page code a little:

    <MudDataGrid ServerData="LoadGridData" T="AnimalListItemDto" 
        RowClick="OnRowClick" Hover="true" @ref="_dataGrid">
        <Columns>
            <PropertyColumn Property="@(item => item.Name)" Title="Animal" />
            <TemplateColumn>
                <CellTemplate>
                    <button @onclick="() => EditItem(context.Item!)" @onclick:stopPropagation>Edit</button>
                </CellTemplate>
            </TemplateColumn>
         </Columns>
        <PagerContent>
            <MudDataGridPager T="AnimalListItemDto" />
        </PagerContent>
    </MudDataGrid>
    

    So now we have to implement a couple of new methods in our code:

    private async Task OnRowClick(DataGridRowClickEventArgs<AnimalListItemDto> args)
    {
        YourViewMethod(args.Item);
    }
    
    private void EditItem(AnimalListItemDto item)
    {
        YourEditMethod(item);
    }
    

    How do I send additional filter / search criteria on the API call?

    Now we want the user to be able to search the Animal data, by putting keywords into a search box.

    Firstly we need to add a SearchTerm property to our request DTO:

    public class GridDataRequestDto
    {
        public string? SearchTerm { get; set; } = null;
        public int Page { get; set; } = 0; // The page number for the data we're requesting
        public int PageSize { get; set; } = 10; // The number of items per page
    }
    

    Then, we add a form to the Blazor page, above the grid:

    <EditForm Model="_requestDto" OnValidSubmit="Search">
        <InputText placeholder="e.g. Animal Name" @bind-Value="_requestDto.SearchTerm" />
        <button type="submit">Search</button>
    </EditForm>
    

    Now we add the Search method:

    private async Task Search()
    {
        if (_dataGrid is not null)
        {
            await _dataGrid!.ReloadServerData();
        }
    }
    

    Now the SearchTerm property is naturally sent along to the API call. You just need to modify your DB query to handle it.

    How do I pre-set the Page and PageSize of the GridState object?

    This might be a niche requirement, but you might want to use persisted state to pre-set the data grid to load a different page of data to the default. You might want to do this if the user has left the page, and then returned, expecting the data to be on the page where they left it.

    Firstly, you need to load the state into the request DTO. So, you need to implement a state manager, e.g. an instance of type GridDataRequestDto called AnimalListState that you inject into your page by adding a scoped MyStateHelper service to your Program.cs builder.Services. Then in your blazor page code:

    @inject IMyStateHelper MyStateHelper;
    
    protected override async Task OnParametersSetAsync()
    {
        _requestDto = MyStateHelper.AnimalListState;
    }
    

    That way, the request DTO has been pre-populated when it comes to render time. So the final step is to to tell the DataGrid to pre-set the Page and PageSize. MudBlazor haven't given us a very nice way to do it, but I find that this approach works:

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && _dataGrid?.HasPager == true)
        {
            _dataGrid!.CurrentPage = _requestDto.Page;
            await _dataGrid.SetRowsPerPageAsync(_requestDto.PageSize);
        }
    }
    

    That's all folks. Please let me know if you have any problems.