Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.1.3</Version>
<Version>10.1.4-beta07</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
17 changes: 11 additions & 6 deletions src/BootstrapBlazor/Converter/JsonFilterKeyValueActionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -89,7 +85,7 @@ public override void Write(Utf8JsonWriter writer, FilterKeyValueAction value, Js
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));
Expand All @@ -109,4 +105,13 @@ public override void Write(Utf8JsonWriter writer, FilterKeyValueAction value, Js

writer.WriteEndObject();
}

Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WriteFieldValueType method lacks XML documentation comments. Consider adding a summary that describes the method's purpose and documents its parameters (writer, value, options) to maintain consistency with the public API documentation style in this codebase.

Suggested change
/// <summary>
/// Writes the type information of the <c>FieldValue</c> property to the JSON output if it is not null.
/// </summary>
/// <param name="writer">The <see cref="Utf8JsonWriter"/> to write to.</param>
/// <param name="value">The <see cref="FilterKeyValueAction"/> instance containing the field value.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for serialization.</param>

Copilot uses AI. Check for mistakes.
private static void WriteFieldValueType(Utf8JsonWriter writer, FilterKeyValueAction value, JsonSerializerOptions options)
{
if (value.FieldValue != null)
{
var type = value.FieldValue.GetType();
writer.WriteString("fieldValueType", type.AssemblyQualifiedName);
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using AssemblyQualifiedName for type serialization can potentially allow arbitrary type instantiation during deserialization if the JSON is received from untrusted sources. While the code uses GetSafeType with throwOnError: false, which prevents exceptions, it still allows any type that can be loaded to be instantiated. Consider implementing a whitelist of allowed types or adding validation to ensure only expected types can be deserialized through this mechanism.

Copilot uses AI. Check for mistakes.
}
}
}
57 changes: 50 additions & 7 deletions src/BootstrapBlazor/Converter/JsonQueryPageOptionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
Expand Down Expand Up @@ -229,8 +225,7 @@ public override void Write(Utf8JsonWriter writer, QueryPageOptions value, JsonSe
}
if (value.SearchModel != null)
{
writer.WritePropertyName("searchModel");
writer.WriteRawValue(JsonSerializer.Serialize(value.SearchModel, options));
WriteSearchModel(writer, value.SearchModel, options);
}
if (value.PageIndex > 1)
{
Expand Down Expand Up @@ -302,4 +297,52 @@ public override void Write(Utf8JsonWriter writer, QueryPageOptions value, JsonSe
}
writer.WriteEndObject();
}

Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WriteSearchModel method lacks XML documentation comments. Consider adding a summary that describes the method's purpose and documents its parameters (writer, value, options) to maintain consistency with the public API documentation style in this codebase.

Suggested change
/// <summary>
/// Serializes the specified search model object into a JSON structure with type information.
/// </summary>
/// <param name="writer">The <see cref="Utf8JsonWriter"/> to write the JSON to.</param>
/// <param name="value">The search model object to serialize.</param>
/// <param name="options">The serializer options to use when serializing the object.</param>

Copilot uses AI. Check for mistakes.
private static void WriteSearchModel(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
writer.WriteStartObject("searchModel");
writer.WriteString("type", value.GetType().AssemblyQualifiedName);
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using AssemblyQualifiedName for type serialization can potentially allow arbitrary type instantiation during deserialization if the JSON is received from untrusted sources. While the code uses GetSafeType with throwOnError: false, which prevents exceptions, it still allows any type that can be loaded to be instantiated. Consider implementing a whitelist of allowed types or adding validation to ensure only expected types can be deserialized through this mechanism.

Copilot uses AI. Check for mistakes.
writer.WritePropertyName("value");
writer.WriteRawValue(JsonSerializer.Serialize(value, options));
writer.WriteEndObject();
}

Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ReadSearchModel method lacks XML documentation comments. Consider adding a summary that describes the method's purpose and documents its parameters (reader, value, options) to maintain consistency with the public API documentation style in this codebase.

Suggested change
/// <summary>
/// Reads and deserializes the <c>searchModel</c> property from the JSON input and assigns it to the <see cref="QueryPageOptions.SearchModel"/> property of the specified <paramref name="value"/> instance.
/// </summary>
/// <param name="reader">The <see cref="Utf8JsonReader"/> to read from. Passed by reference.</param>
/// <param name="value">The <see cref="QueryPageOptions"/> instance to assign the deserialized search model to.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for deserialization.</param>

Copilot uses AI. Check for mistakes.
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);
}
}
Comment on lines +329 to +342
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deserialization logic assumes the JSON properties "type" and "value" appear in a specific order, but JSON object property order is not guaranteed by the specification. If the "value" property appears before "type" in the JSON, the type variable will be null and deserialization will fall back to using JsonElement. Consider reading both properties first into local variables before attempting deserialization, or handle the case where properties might appear in any order.

Copilot uses AI. Check for mistakes.
}
}
}
}
}
}
18 changes: 17 additions & 1 deletion src/BootstrapBlazor/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -64,4 +64,20 @@ void EnsureNoAuthenticationSchemeSpecified()
public static string GetUniqueTypeName(this Type type) => type.IsCollectible
? $"{type.FullName}-{type.TypeHandle.Value}"
: $"{type.FullName}";


Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra blank line should be removed to follow consistent code formatting conventions within this file.

Suggested change

Copilot uses AI. Check for mistakes.
/// <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;
}
}
74 changes: 73 additions & 1 deletion test/UnitTest/Extensions/QueryPageOptionsExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public async Task Serialize_Ok()
IsTriggerByPagination = true,
IsPage = true,
IsVirtualScroll = true,
SearchModel = new { Name = "Test1", Count = 2 }
SearchModel = new Foo { Name = "Test1", Count = 2 }
};

model.Filters.Add(cut.Instance);
Expand All @@ -196,6 +196,11 @@ public async Task Serialize_Ok()
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);
Comment on lines +200 to +202
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verifies that the Name property is correctly deserialized, but it does not verify that the Count property (which is also set to 2 on line 185) is correctly deserialized. Consider adding an assertion like Assert.Equal(2, foo.Count); after line 202 to ensure complete test coverage of the SearchModel deserialization.

Copilot uses AI. Check for mistakes.

Assert.Equal("Name1", expected.SortName);
Assert.Equal(3, expected.StartIndex);
Assert.Equal(4, expected.PageIndex);
Expand All @@ -216,6 +221,73 @@ public async Task Serialize_Ok()
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()
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test method name "SearchModel_Serialize_NullType" is misleading. The test does not check null type handling but rather checks handling of an invalid/non-existent type name (by replacing "FooSearchModel" with "FooSearchModel1"). A more accurate name would be "SearchModel_Serialize_InvalidType" or "SearchModel_Serialize_UnknownType".

Suggested change
public void SearchModel_Serialize_NullType()
public void SearchModel_Serialize_InvalidType()

Copilot uses AI. Check for mistakes.
{
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()
{
Expand Down
Loading