You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.)
 ---&gt; System.NotSupportedException: DeserializeNoConstructor, JsonConstructorAttribute, System.Dynamic.ExpandoObject Path: $ | LineNumber: 0 | BytePositionInLine: 1.
 ---&gt; System.NotSupportedException: DeserializeNoConstructor, JsonConstructorAttribute, System.Dynamic.ExpandoObject
   Exception_EndOfInnerExceptionStack
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack&amp; , Utf8JsonReader&amp; , NotSupportedException )
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(Type , Utf8JsonReader&amp; , ReadStack&amp; )
   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&amp; , ReadStack&amp; )
   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&amp; , ReadStack&amp; )
   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&amp; , Type , JsonSerializerOptions , ReadStack&amp; , ExpandoObject&amp; )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].TryRead(Utf8JsonReader&amp; , Type , JsonSerializerOptions , ReadStack&amp; , ExpandoObject&amp; , Boolean&amp; )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Dynamic.ExpandoObject, System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].ReadCore(Utf8JsonReader&amp; , JsonSerializerOptions , ReadStack&amp; )
   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&amp; , ReadStack&amp; )
   at System.Text.Json.JsonSerializer.Read[ExpandoObject](Utf8JsonReader&amp; , JsonTypeInfo`1 )
   at System.Text.Json.JsonSerializer.Deserialize[ExpandoObject](Utf8JsonReader&amp; , JsonSerializerOptions )
   at Shared.Helpers.ExpandoObjectConverter.Read(Utf8JsonReader&amp; 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 09:02:04