feat(QueryPageOptions): SearchModel support serialize (#7327)

* refactor: 代码重构增加 type 标识类型

* test: 更新单元测试

* test: 补充单元测试

* test: 增加单元测试提高覆盖率

* chore: bump version 10.1.4-beta07
This commit is contained in:
Argo Zhang
2025-12-14 11:09:02 +08:00
committed by GitHub
parent 258c5465d4
commit 894e27cd1d
5 changed files with 152 additions and 16 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>10.1.3</Version>
<Version>10.1.4-beta07</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -42,11 +42,7 @@ public sealed class JsonFilterKeyValueActionConverter : JsonConverter<FilterKeyV
action.FieldKey = reader.GetString();
break;
case "fieldValueType":
var typeName = reader.GetString();
if (!string.IsNullOrEmpty(typeName))
{
fieldValueType = Type.GetType(typeName);
}
fieldValueType = TypeExtensions.GetSafeType(reader.GetString());
break;
case "fieldValue":
if (fieldValueType != null)
@@ -89,7 +85,7 @@ public sealed class JsonFilterKeyValueActionConverter : JsonConverter<FilterKeyV
writer.WriteStartObject();
writer.WriteString("fieldKey", value.FieldKey);
writer.WriteString("fieldValueType", value.FieldValue?.GetType().FullName);
WriteFieldValueType(writer, value, options);
writer.WritePropertyName("fieldValue");
writer.WriteRawValue(JsonSerializer.Serialize(value.FieldValue, options));
@@ -109,4 +105,13 @@ public sealed class JsonFilterKeyValueActionConverter : JsonConverter<FilterKeyV
writer.WriteEndObject();
}
private static void WriteFieldValueType(Utf8JsonWriter writer, FilterKeyValueAction value, JsonSerializerOptions options)
{
if (value.FieldValue != null)
{
var type = value.FieldValue.GetType();
writer.WriteString("fieldValueType", type.AssemblyQualifiedName);
}
}
}

View File

@@ -73,11 +73,7 @@ public sealed class JsonQueryPageOptionsConverter : JsonConverter<QueryPageOptio
else if (propertyName == "searchModel")
{
reader.Read();
var val = JsonSerializer.Deserialize<object>(ref reader, options);
if (val != null)
{
ret.SearchModel = val;
}
ReadSearchModel(ref reader, ret, options);
}
else if (propertyName == "pageIndex")
{
@@ -229,8 +225,7 @@ public sealed class JsonQueryPageOptionsConverter : JsonConverter<QueryPageOptio
}
if (value.SearchModel != null)
{
writer.WritePropertyName("searchModel");
writer.WriteRawValue(JsonSerializer.Serialize(value.SearchModel, options));
WriteSearchModel(writer, value.SearchModel, options);
}
if (value.PageIndex > 1)
{
@@ -302,4 +297,52 @@ public sealed class JsonQueryPageOptionsConverter : JsonConverter<QueryPageOptio
}
writer.WriteEndObject();
}
private static void WriteSearchModel(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
writer.WriteStartObject("searchModel");
writer.WriteString("type", value.GetType().AssemblyQualifiedName);
writer.WritePropertyName("value");
writer.WriteRawValue(JsonSerializer.Serialize(value, options));
writer.WriteEndObject();
}
private static void ReadSearchModel(ref Utf8JsonReader reader, QueryPageOptions value, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartObject)
{
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
if (propertyName == "type")
{
reader.Read();
Type? type = TypeExtensions.GetSafeType(reader.GetString());
reader.Read();
propertyName = reader.GetString();
if (propertyName == "value")
{
reader.Read();
if (type != null)
{
value.SearchModel = JsonSerializer.Deserialize(ref reader, type, options);
}
else
{
value.SearchModel = JsonSerializer.Deserialize<object>(ref reader, options);
}
}
}
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
@@ -64,4 +64,20 @@ internal static class TypeExtensions
public static string GetUniqueTypeName(this Type type) => type.IsCollectible
? $"{type.FullName}-{type.TypeHandle.Value}"
: $"{type.FullName}";
/// <summary>
/// 通过 typeName 参数安全获取 Type 实例
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
public static Type? GetSafeType(string? typeName)
{
Type? type = null;
if (!string.IsNullOrEmpty(typeName))
{
type = Type.GetType(typeName, throwOnError: false);
}
return type;
}
}

View File

@@ -182,7 +182,7 @@ public class QueryPageOptionsExtensionsTest : BootstrapBlazorTestBase
IsTriggerByPagination = true,
IsPage = true,
IsVirtualScroll = true,
SearchModel = new { Name = "Test1", Count = 2 }
SearchModel = new Foo { Name = "Test1", Count = 2 }
};
model.Filters.Add(cut.Instance);
@@ -196,6 +196,11 @@ public class QueryPageOptionsExtensionsTest : BootstrapBlazorTestBase
var expected = JsonSerializer.Deserialize<QueryPageOptions>(payload);
Assert.NotNull(expected);
Assert.Equal("SearchText", expected.SearchText);
var foo = expected.SearchModel as Foo;
Assert.NotNull(foo);
Assert.Equal("Test1", foo.Name);
Assert.Equal("Name1", expected.SortName);
Assert.Equal(3, expected.StartIndex);
Assert.Equal(4, expected.PageIndex);
@@ -216,6 +221,73 @@ public class QueryPageOptionsExtensionsTest : BootstrapBlazorTestBase
Assert.Equal(2, expected.AdvancedSortList.Count);
}
[Fact]
public void SearchModel_Serialize()
{
var model = new QueryPageOptions
{
SearchModel = new { Name = "Test1", Count = 2 }
};
var payload = JsonSerializer.Serialize(model);
var expected = JsonSerializer.Deserialize<QueryPageOptions>(payload);
Assert.NotNull(expected);
Assert.NotNull(expected.SearchModel);
}
[Fact]
public void SearchModel_Serialize_TableSearchModel()
{
var model = new QueryPageOptions
{
SearchModel = new FooSearchModel { Name = "Test1" }
};
var payload = JsonSerializer.Serialize(model);
var expected = JsonSerializer.Deserialize<QueryPageOptions>(payload);
Assert.NotNull(expected);
Assert.NotNull(expected.SearchModel);
Assert.Equal("FooSearchModel", expected.SearchModel.GetType().Name);
}
[Fact]
public void SearchModel_Serialize_NullType()
{
var model = new QueryPageOptions
{
SearchModel = new FooSearchModel { Name = "Test1" }
};
var payload = JsonSerializer.Serialize(model);
// 更改为错误的类型
payload = payload.Replace("FooSearchModel", "FooSearchModel1");
var expected = JsonSerializer.Deserialize<QueryPageOptions>(payload);
Assert.NotNull(expected);
Assert.NotNull(expected.SearchModel);
Assert.Equal("JsonElement", expected.SearchModel.GetType().Name);
}
private class FooSearchModel : ITableSearchModel
{
public string? Name { get; set; }
public IEnumerable<IFilterAction> GetSearches()
{
var ret = new List<IFilterAction>();
if (!string.IsNullOrEmpty(Name))
{
ret.Add(new SearchFilterAction(nameof(Foo.Name), Name));
}
return ret;
}
public void Reset()
{
Name = null;
}
}
[Fact]
public void SerializeFilterAction_Ok()
{