4.15.0 Save/Load settings DataGrid columns without Property Broken

Run demo Blazor DataGrid save/load settings with 2+ columns without Property Broken. After save setting and load, browser freezes, only clearing the cache helps (delete settings)

@using Radzen
@using RadzenBlazorDemos.Data
@using RadzenBlazorDemos.Models.Northwind
@using Microsoft.EntityFrameworkCore
@using RadzenBlazorDemos.Services
@using Microsoft.JSInterop
@using System.Text.Json

@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager

@inherits DbContextPage

<p>This example shows how to save/load DataGrid state using Settings property.</p>
<p>The state includes current page index, page size, groups and columns filter, sort, order, width and visibility.</p>
<RadzenButton Click="@(args => Settings = null)" Text="Clear saved settings" Style="margin-bottom: 16px" />
<RadzenButton Click="@(args => NavigationManager.NavigateTo("/datagrid-save-settings", true))" Text="Reload" Style="margin-bottom: 16px" />
<RadzenDataGrid @bind-Settings="@Settings" AllowFiltering="true" AllowColumnPicking="true" AllowGrouping="true" AllowPaging="true" PageSize="4"
                AllowSorting="true" AllowMultiColumnSorting="true" ShowMultiColumnSortingIndex="true" 
                AllowColumnResize="true" AllowColumnReorder="true" ColumnWidth="200px"
                FilterPopupRenderMode="PopupRenderMode.OnDemand" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" Data="@employees" TItem="Employee">
    <Columns>
        <RadzenDataGridColumn TItem="Employee" Title="Employee" Sortable="false" Filterable="false">
            <Template Context="data">
                <RadzenImage Path="@data.Photo" style="width: 40px; height: 40px; border-radius: 8px; margin-right: 8px;" />
                @data.FirstName @data.LastName
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn TItem="Employee" Title="Employee2" Sortable="false" Filterable="false">
            <Template Context="data">
                <RadzenImage Path="@data.Photo" style="width: 40px; height: 40px; border-radius: 8px; margin-right: 8px;" />
                @data.FirstName @data.LastName
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn TItem="Employee" Property="Title" Title="Title" />
        <RadzenDataGridColumn TItem="Employee" Property="EmployeeID" Title="Employee ID" />
        <RadzenDataGridColumn TItem="Employee" Property="HireDate" Title="Hire Date" FormatString="{0:d}" />
        <RadzenDataGridColumn TItem="Employee" Property="City" Title="City" />
        <RadzenDataGridColumn TItem="Employee" Property="Country" Title="Country" />
    </Columns>
</RadzenDataGrid>

<EventConsole @ref=@console />

@code {
    IEnumerable<Employee> employees;
    EventConsole console;

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        employees = dbContext.Employees;
    }

    DataGridSettings _settings;
    public DataGridSettings Settings 
    { 
        get
        {
            return _settings;
        }
        set
        {
            if (_settings != value)
            {
                _settings = value;
                InvokeAsync(SaveStateAsync);
            }
        }
    }

    private async Task LoadStateAsync()
    {
        await Task.CompletedTask;

        var result = await JSRuntime.InvokeAsync<string>("window.localStorage.getItem", "Settings");
        if (!string.IsNullOrEmpty(result))
        {
            _settings = JsonSerializer.Deserialize<DataGridSettings>(result);
        }
    }

    private async Task SaveStateAsync()
    {
        await Task.CompletedTask;

        await JSRuntime.InvokeVoidAsync("eval", $@"window.localStorage.setItem('Settings', '{JsonSerializer.Serialize<DataGridSettings>(Settings)}')");
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await LoadStateAsync();
            StateHasChanged();
        }
    }
}

Works normally to me

Sandbox with clear windows:

WindowsSandboxClient_2023-08-10_15-12-53

And may be this also broke the work simple filters with enum on the page with saving / loading settings. The previous version works without problems.

Combination code for two demo pages:

@using System.Linq.Dynamic.Core
@using System.ComponentModel.DataAnnotations
@using System.Text.Json           
@inject IJSRuntime JSRuntime

<RadzenDataGrid LoadData="LoadData" IsLoading=@isLoading Count="count" Data=@employees @bind-Settings="@Settings"
                FilterMode="FilterMode.Simple" AllowFiltering="true" AllowPaging="true" AllowSorting="true" TItem="Employee" ColumnWidth="200px">
	<Columns>
		<RadzenDataGridColumn TItem="Employee" Property="ID" Title="ID" />
		<RadzenDataGridColumn TItem="Employee" Property="Gender" Title="Gender" />
		<RadzenDataGridColumn TItem="Employee" Property="Status" Title="Nullable Status" />
		<RadzenDataGridColumn TItem="Employee" Property="Color" Title="Favorite Color (Display Attribute in Filter)" />
	</Columns>
</RadzenDataGrid>

<EventConsole @ref=@console />

@code {             

	EventConsole console;
	DataGridSettings _settings;
	public DataGridSettings Settings
	{
		get
		{
			return _settings;
		}
		set
		{
			if (_settings != value)
			{
				_settings = value;
				console.Log("Set");
				InvokeAsync(SaveStateAsync);
			}
		}
	}

	private async Task LoadStateAsync()
	{
		console.Log("LoadStateAsync");
		var result = await JSRuntime.InvokeAsync<string>("window.localStorage.getItem", "SettingsLoadData");
		if (!string.IsNullOrEmpty(result))
		{
			_settings = JsonSerializer.Deserialize<DataGridSettings>(result);
		}
	}

	private async Task SaveStateAsync()
	{
		console.Log("SaveStateAsync");
		await JSRuntime.InvokeVoidAsync("eval", $@"window.localStorage.setItem('SettingsLoadData', '{JsonSerializer.Serialize<DataGridSettings>(Settings)}')");
	}

    int count;
    IEnumerable<Employee> initialEmployees;
    IEnumerable<Employee> employees;
    bool isLoading = false;

    public class Employee
    {
        public int ID { get; set; }
        public GenderType Gender { get; set; }
        public StatusType? Status { get; set; }
        public ColorType Color { get; set; }
    }

    public enum GenderType
    {
        Ms,
        Mr,
        Unknown,
    }

    public enum ColorType
    {
        Red,
        Green,
        Blue,
        [Display(Description = "Almond Green")]
        AlmondGreen,
        [Display(Description = "Amber Gray")]
        AmberGray,
        [Display(Description = "Apple Blue... ")]
        AppleBlueSeaGreen,
        //[Display(Description = "Miss", ResourceType = typeof(ResourceFile)] localization example
        [Display(Description = "Azure")]
        AzureBlue,

    }

    public enum StatusType
    {
        Inactive,
        Active,
    }

    protected override void OnInitialized()
    {
        initialEmployees = Enumerable.Range(0, 10).Select(i =>
            new Employee
                {
                    ID = i,
                    Gender = i < 3 ? GenderType.Mr : i < 6 ? GenderType.Ms : GenderType.Unknown,
                    Status = i < 3 ? StatusType.Active : i < 6 ? StatusType.Inactive : null,
	                Color = i < 2 ? ColorType.Red: i < 4 ? ColorType.AlmondGreen : i < 6 ? ColorType.AppleBlueSeaGreen : ColorType.AzureBlue,
                });
    }

    void LoadData(LoadDataArgs args)
    {
	    console.Log("LoadData");
        isLoading = true;
        var query = initialEmployees.AsQueryable();

        if (!string.IsNullOrEmpty(args.Filter))
        {
            query = query.Where(args.Filter);
        }

        if (!string.IsNullOrEmpty(args.OrderBy))
        {
            query = query.OrderBy(args.OrderBy);
        }

        count = query.Count();

        employees = query.Skip(args.Skip.Value).Take(args.Top.Value).ToList();
        isLoading = false;

    }
}

Example:

2023-08-10_16-56-26

@enchev I found, reverting a commit

solves the problem with simple filters with enum on the page with saving / loading settings :thinking:

Issue with "DataGrid save/load settings with 2+ columns without property" still relevant

Before this commit columns without properties where ignored during save/load of settings. Now every column have UniqueID and save/load settings will work with these columns too.

And that's great, we really need this feature. If not for the problem that I described in the first/second post

You did get bug it on https://blazor.radzen.com/, not on localhost?

I've tested your example code directly in the Visual Studio project:

Check what you have in the result after JsonSerializer.Deserialize() - you might have something invalid.

I've just published 4.15.1 with improvement related to column UniqueID (column index in the collection will be used instead GUID) - might resolve the problem you have:

@enchev thanks for the quick attempt to fix the problem, this idea seems to me better than base64, but it doesn't seem to work as intended
All fields have the same unique ID. :sweat_smile:

I think there is a bug here

If there are 2 or more fields with c.Property == null, then FirstOrDefault always takes the first field.

I tried using only ColumnsCollection.Where(c => c.UniqueID == column.UniqueID).FirstOrDefault()
but the settings are not saved at all

We've decided to not auto set UniqueID for the columns and to restore the old logic where settings for columns without Property will not be saved/loaded. If you need to save/load settings for such column you can set manually UniqueID. Just published Radzen.Blazor 4.15.2.

New demo code:

@using Radzen
@using RadzenBlazorDemos.Data
@using RadzenBlazorDemos.Models.Northwind
@using Microsoft.EntityFrameworkCore
@using RadzenBlazorDemos.Services
@using Microsoft.JSInterop
@using System.Text.Json
@using System.Linq.Dynamic.Core

@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager

@inherits DbContextPage

<RadzenButton Click="@(args => Settings = null)" Text="Clear saved settings" Style="margin-bottom: 16px" />
<RadzenButton Click="@(args => NavigationManager.NavigateTo("/datagrid-save-settings-loaddata", true))" Text="Reload" Style="margin-bottom: 16px" />
<RadzenDataGrid @ref=grid @bind-Settings="@Settings" AllowFiltering="true" AllowColumnPicking="true" AllowGrouping="true" AllowPaging="true" PageSize="4"
                AllowSorting="true" AllowMultiColumnSorting="true" ShowMultiColumnSortingIndex="true"
                AllowColumnResize="true" AllowColumnReorder="true" ColumnWidth="200px"
                FilterPopupRenderMode="PopupRenderMode.OnDemand" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
                Data="@employees" IsLoading=@isLoading Count="@count" LoadData=@LoadData TItem="Employee">
    <Columns>
	    <RadzenDataGridColumn TItem="Employee" Title="Custom 1" Sortable="false" Filterable="false">
		    <Template Context="data">
			    <RadzenImage Path="@data.Photo" style="width: 40px; height: 40px; border-radius: 8px; margin-right: 8px;" />
			    @data.FirstName @data.LastName
		    </Template>
	    </RadzenDataGridColumn>
        <RadzenDataGridColumn TItem="Employee" Title="Custom 2" Sortable="false" Filterable="false">
            <Template Context="data">
                <RadzenImage Path="@data.Photo" style="width: 40px; height: 40px; border-radius: 8px; margin-right: 8px;" />
                @data.FirstName @data.LastName
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn TItem="Employee" Property="Title" Title="Title" />
        <RadzenDataGridColumn TItem="Employee" Property="EmployeeID" Title="Employee ID" />
       @*  <RadzenDataGridColumn TItem="Employee" Property="HireDate" Title="Hire Date" FormatString="{0:d}" />
        <RadzenDataGridColumn TItem="Employee" Property="City" Title="City" />
        <RadzenDataGridColumn TItem="Employee" Property="Country" Title="Country" /> *@
    </Columns>
</RadzenDataGrid>

<EventConsole @ref=@console />

@code {
    RadzenDataGrid<Employee> grid;
    IEnumerable<Employee> employees;
    EventConsole console;

    int count;
    bool isLoading = false;
    bool loaded;
    async Task LoadData(LoadDataArgs args)
    {
        console.Log("LoadData");
        isLoading = true;

        await Task.Yield();

        var query = dbContext.Employees.AsQueryable();

        if (!string.IsNullOrEmpty(args.Filter))
        {
            query = query.Where(args.Filter);
        }

        if (!string.IsNullOrEmpty(args.OrderBy))
        {
            query = query.OrderBy(args.OrderBy);
        }

        count = query.Count();

        // Simulate async data loading
        await Task.Delay(200);

        employees = await Task.FromResult(query.Skip(args.Skip.Value).Take(args.Top.Value).ToList());

        isLoading = false;

        loaded = true;
    }

    DataGridSettings _settings;
    public DataGridSettings Settings
    {
        get
        {
            return _settings;
        }
        set
        {
            if (_settings != value)
            {
                _settings = value;
                console.Log("Set");
                InvokeAsync(SaveStateAsync);
            }
        }
    }

    private async Task LoadStateAsync()
    {
        console.Log("LoadStateAsync");
        var result = await JSRuntime.InvokeAsync<string>("window.localStorage.getItem", "SettingsLoadData");
        if (!string.IsNullOrEmpty(result))
        {
            _settings = JsonSerializer.Deserialize<DataGridSettings>(result);
        }
    }

    private async Task SaveStateAsync()
    {
        console.Log("SaveStateAsync");
        await JSRuntime.InvokeVoidAsync("eval", $@"window.localStorage.setItem('SettingsLoadData', '{JsonSerializer.Serialize<DataGridSettings>(Settings)}')");
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender || loaded)
        {
            await LoadStateAsync();

            if (loaded)
            {
                loaded = false;
                await Task.Yield();
                await grid.Reload();
                loaded = false;
            }
        }
    }
}

chrome_2023-08-10_20-40-02

Check my previous reply.