Blazor WebAssembly代码仅发布后抛出异常问题排查
Blazor WASM发布后ExpandoObject反序列化异常问题
问题背景
实现了自定义ExpandoObjectConverter转换器,配合StringContainer辅助类使用,在所有平台及Blazor WASM Debug/Release直接运行时正常,但发布后抛出反序列化异常,提示DeserializeNoConstructor, JsonConstructorAttribute, System.Dynamic.ExpandoObject。已排除BOM问题,未手动设置代码裁剪。
自定义转换器代码
public class ExpandoObjectConverter : JsonConverter<dynamic> { private static readonly ExpandoObjectConverter self = new(); public override dynamic Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // By default, it is not using itself for deeper levels in the object. if (!options.Converters.OfType<ExpandoObjectConverter>().Any()) { options = new JsonSerializerOptions(options); options.Converters.Add(self); } if (reader.TokenType == JsonTokenType.StartObject) { return JsonSerializer.Deserialize<ExpandoObject>(ref reader, options); } else if (reader.TokenType == JsonTokenType.StartArray) { var list = new List<object>(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndArray) { break; } if (reader.TokenType == JsonTokenType.StartObject) { list.Add(JsonSerializer.Deserialize<ExpandoObject>(ref reader, options)); } else { list.Add(GetValue(ref reader)); } } return list.ToArray(); } else { return GetValue(ref reader); } } public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, value, options); } private object GetValue(ref Utf8JsonReader reader) => reader.TokenType switch { JsonTokenType.False or JsonTokenType.True => reader.GetBoolean(), JsonTokenType.Null => null, JsonTokenType.Number => reader.GetDecimal(), _ => reader.GetString() }; }
辅助类使用代码
public class StringContainer { private readonly JsonSerializerOptions opts = new() { Converters = { new ExpandoObjectConverter() } }; public StringContainer( ILanguageFileProvider languageFileProvider) { this.logger = logger; var json = languageFileProvider.GetLanguageFile(); ExpandoObject? inter = JsonSerializer.Deserialize<dynamic>(json, opts); ... } .... }
发布后异常信息
blazor.webassembly.js:1 System.AggregateException: AggregateException_ctor_DefaultMessage (DeserializeNoConstructor, JsonConstructorAttribute, System.Dynamic.ExpandoObject Path: $ | LineNumber: 0 | BytePositionInLine: 1.) ---> System.NotSupportedException: DeserializeNoConstructor, JsonConstructorAttribute, System.Dynamic.ExpandoObject Path: $ | LineNumber: 0 | BytePositionInLine: 1. ---> System.NotSupportedException: DeserializeNoConstructor, JsonConstructorAttribute, System.Dynamic.ExpandoObject Exception_EndOfInnerExceptionStack at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& , Utf8JsonReader& , NotSupportedException ) at System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(Type , Utf8JsonReader& , ReadStack& ) at System.Text.Json.Serialization.JsonDictionaryConverter`3[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[System.String, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].CreateCollection(Utf8JsonReader& , ReadStack& ) at System.Text.Json.Serialization.Converters.IDictionaryOfTKeyTValueConverter`3[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[System.String, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].CreateCollection(Utf8JsonReader& , ReadStack& ) at System.Text.Json.Serialization.JsonDictionaryConverter`3[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[System.String, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , ExpandoObject& ) at System.Text.Json.Serialization.JsonConverter`1[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , ExpandoObject& , Boolean& ) at System.Text.Json.Serialization.JsonConverter`1[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].ReadCore(Utf8JsonReader& , JsonSerializerOptions , ReadStack& ) at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].Deserialize(Utf8JsonReader& , ReadStack& ) at System.Text.Json.JsonSerializer.Read[ExpandoObject](Utf8JsonReader& , JsonTypeInfo`1 ) at System.Text.Json.JsonSerializer.Deserialize[ExpandoObject](Utf8JsonReader& , JsonSerializerOptions ) at Shared.Helpers.ExpandoObjectConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options) ...
解决方案
1. 修复转换器中ExpandoObject的创建逻辑
异常核心是发布后AOT编译导致ExpandoObject的无参构造被优化或无法通过反射调用,转换器中直接调用JsonSerializer.Deserialize<ExpandoObject>依赖反射构造,发布后失效。修改转换器中对象创建部分,手动实例化ExpandoObject并填充键值对:
修改Read方法中JsonTokenType.StartObject分支:
if (reader.TokenType == JsonTokenType.StartObject) { var expando = new ExpandoObject(); var dict = (IDictionary<string, object>)expando; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) break; if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException("Expected property name"); string propertyName = reader.GetString(); reader.Read(); dict[propertyName] = Read(ref reader, typeof(object), options); } return expando; }
2. 调整发布配置(禁用AOT或保留裁剪规则)
- 临时禁用AOT:打开WASM项目的
.csproj文件,添加以下配置:
<PropertyGroup> <RunAOTCompilation>false</RunAOTCompilation> </PropertyGroup>
- 保留AOT并配置裁剪规则:在
.csproj中添加:
<ItemGroup> <TrimmerRootAssembly Include="System.Linq.Expressions" /> <TrimmerRootDescriptor Include="ExpandoObjectTrim.xml" /> </ItemGroup>
然后创建ExpandoObjectTrim.xml文件,内容:
<linker> <assembly fullname="System.Linq.Expressions"> <type fullname="System.Dynamic.ExpandoObject" preserve="all" /> </assembly> </linker>
3. 替换为原生动态JSON处理
放弃自定义转换器,直接使用JsonNode或JsonElement处理动态JSON,避免依赖ExpandoObject的反射构造:
// 替换原反序列化代码 JsonNode? inter = JsonSerializer.Deserialize<JsonNode>(json, opts); // 访问示例:string value = inter["key"].ToString();
关键原因
Blazor WASM发布时默认启用AOT编译和IL裁剪,ExpandoObject的无参构造及相关反射依赖被优化移除,导致运行时无法实例化对象。Debug/Release直接运行时使用JIT,反射可正常调用构造,因此无异常。
内容的提问来源于stack exchange,提问作者ZorgoZ
相关产品推荐
相关产品推荐

