IJSRuntime 忽略服务器端 Blazor 项目中的自定义 json 序列化程序

Posted

技术标签:

【中文标题】IJSRuntime 忽略服务器端 Blazor 项目中的自定义 json 序列化程序【英文标题】:IJSRuntime ignores custom json serializer in server side blazor project 【发布时间】:2019-11-16 19:17:36 【问题描述】:

在我的服务器端 blazor 项目(核心 3,preview6)中,我试图用我的类的一个实例调用 javascript。您可以通过注入 IJSRuntime 并在其上调用 InvokeAsync 来做到这一点(请参阅 here)。

因为我需要摆脱所有 null 属性(应该处理我的对象 (chartJs) 的 js 库不能处理 null 值)并且因为我有一些自定义序列化(对于可以具有不同数据类型的枚举),所以我破解了它与ExpandoObject 一起工作(这不是我的想法,但它有效)。

我首先用 json.net 序列化了我的对象,这样我就可以定义 NullValueHandling = NullValueHandling.Ignore 并使用所有自定义转换器。

然后我再次使用 json.net 将其解析为 ExpandoObject。我当然这样做了,否则 json 中不存在的值在 c# 实例中将为 null,但这样它们根本不存在。

这个ExpandoObject 没有空值并且具有正确的枚举值,然后用于调用javascript。这样一来,当它到达 javascript 引擎时,就没有 null 值了。

Since in preview6 json.net isn't used internally anymore 和新的 json 序列化程序(来自 System.Text.Json)无法处理 ExpandoObjects,当我从 preview5 更新到 preview6 时出现运行时错误,说不支持 ExpandoObject。没什么大不了的,我做了一个函数来将 ExpandoObject 转换为 Dictionary<string, object> 可以毫无问题地序列化。

阅读this question 了解更多关于我是如何做到这一点的并查看一些示例。如果你不太明白我做了什么,这可能是个好主意。它还包含我将在此处添加的 json 以及 c# 模型和其他说明。

这可行,但我想如果我能够回到 json.net,我可以完全删除 ExpandoObject,因为首先它会使用我为特殊枚举编写的自定义转换器,其次我可以指定 @ 987654341@ 全局(ps。这会有副作用吗?)。

我关注the ms docs 再次了解如何使用 json.net 并将其添加到 ConfigureServies 方法中(services.AddRazorPages() 已经存在):

services.AddRazorPages()
    .AddNewtonsoftJson(o =>
    
        o.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
        o.SerializerSettings.ContractResolver = new DefaultContractResolver
        
            NamingStrategy = new CamelCaseNamingStrategy(true, false)
        ;
    );

遗憾的是,它不起作用,直接使用对象调用 js 会导致与我添加它之前完全相同的 json(见下文)。

然后我尝试直接使用ExpandoObject,因为我知道 .net 的新 json 序列化程序无法处理。正如预期的那样,它引发了异常,并且堆栈跟踪没有显示 json.net 的迹象。这证实了它实际上并没有像我告诉它的那样使用 json.net。

在 System.Text.Json.Serialization.JsonClassInfo.GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo) 在 System.Text.Json.Serialization.JsonClassInfo.CreateProperty(类型声明的PropertyType,类型 runtimePropertyType,PropertyInfo propertyInfo,类型 parentClassType,JsonSerializerOptions 选项) 在 System.Text.Json.Serialization.JsonClassInfo.AddProperty(类型 propertyType,PropertyInfo propertyInfo,Type classType,JsonSerializerOptions 选项) 在 System.Text.Json.Serialization.JsonClassInfo..ctor(类型类型,JsonSerializerOptions 选项) 在 System.Text.Json.Serialization.JsonSerializerOptions.GetOrAddClass(类型 classType) 在 System.Text.Json.Serialization.JsonSerializer.GetRuntimeClassInfo(对象值,JsonClassInfo& jsonClassInfo,JsonSerializerOptions 选项) 在 System.Text.Json.Serialization.JsonSerializer.HandleEnumerable(JsonClassInfo elementClassInfo,JsonSerializerOptions 选项,Utf8JsonWriter writer,WriteStack& 状态) 在 System.Text.Json.Serialization.JsonSerializer.Write(Utf8JsonWriter writer,Int32 flushThreshold,JsonSerializerOptions 选项,WriteStack& 状态) 在 System.Text.Json.Serialization.JsonSerializer.WriteCore(PooledByteBufferWriter 输出,对象值,类型类型,JsonSerializerOptions 选项) 在 System.Text.Json.Serialization.JsonSerializer.WriteCoreString(对象值,类型类型,JsonSerializerOptions 选项) 在 System.Text.Json.Serialization.JsonSerializer.ToString[TValue](TValue 值,JsonSerializerOptions 选项) 在 Microsoft.JSInterop.JSRuntimeBase.InvokeAsync[T](字符串标识符,对象 [] args) 在 ChartJs.Blazor.ChartJS.ChartJsInterop.GetJsonRep(IJSRuntime jSRuntime, Object obj)

然后我还尝试只更改普通序列化程序的选项,看看是否会改变任何东西。我用这个代替AddNewtonsoftJson

services.AddRazorPages()
    .AddJsonOptions(o => o.JsonSerializerOptions.IgnoreNullValues = true);

有趣的是,这仍然给了我相同的结果。

我将添加生成的 json 以及应该生成的内容。您还可以在我之前提到的 CodeReview 问题中找到这一点。

以下 json 是我得到的:

它保持不变

我没有在 ConfigureServices 方法中指定任何内容。 我指定使用Newtonsoft.JsonAddNewtonsoftJson。 我从.net 到AddJsonOptions 指定JsonOptions

    "options": 
        "someInt": 2,
        "someString": null,
        "axes": [
            
                "someString": null
            ,
            
                "someString": "axisString"
            
        ]
    ,
    "data": 
        "data": [
            1,
            2,
            3,
            4,
            5
        ],
        "someString": "asdf",
        "someStringEnum":    <-- this is one of those special enums with a custom converter
    

这是我应该得到的。请注意,所有空值都已删除,并使用了自定义转换器。我只能通过使用 ExpandoObject 的 hacky 解决方案来实现这一点。


    "options": 
        "someInt": 2,
        "axes": [
            ,
            
                "someString": "axisString"
            
        ]
    ,
    "data": 
        "data": [
            1,
            2,
            3,
            4,
            5
        ],
        "someString": "asdf",
        "someStringEnum": "someTestThing"   <-- this is one of those special enums with a custom converter
    

那么为什么即使我指示它仍然使用System.Text.Json 而不是Newtonsoft.Json?我还需要添加什么才能使其使用 json.net?

【问题讨论】:

检查reference source,被调用的方法Microsoft.JSInterop.JSRuntimeBase.InvokeAsync似乎硬编码为使用System.Text.Json.Serialization.JsonSerializer。您可能需要创建自己的IJSRuntime 该死的 :( 但是我怎么能创建自己的?我认为在 javascript 中注入 jsruntime 有一个相当大的生态系统吗? 既然您要注入自己的IJSRuntime,也许您可​​以使用decorator pattern 并为“真实”IJSRuntime 创建一些包装器,将args 序列化和预处理为干净的中性格式? IE。 arg -> JToken(通过序列化选项)-> Dictionary&lt;string, object&gt; 如How do I use JSON.NET to deserialize into nested/recursive Dictionary and List? 所示。 我应该这样回答吗? 对不起@dbc。我想我没有完全理解你试图告诉我的内容。您的解决方案可能会奏效,而我的一些主张根本不准确。我没有尝试过,但是在寻找解决这个问题的方法并写a new feature request for this时,我想我终于明白你的意思了。谢谢你的帮助:) 【参考方案1】:

正如@dbc 在 cmets 中正确提到的,IJSRuntime 始终使用 System.Text.Json。我已经通过询问 asp.net 团队确认了这一点(请参阅my github issue)。

我提交了github feature request,这可能会在未来的 .NET 版本中实现。

【讨论】:

以上是关于IJSRuntime 忽略服务器端 Blazor 项目中的自定义 json 序列化程序的主要内容,如果未能解决你的问题,请参考以下文章

Hello Blazor:(15)使用bUnit进行单元测试

Blazor - 从 C# 类调用 JavaScript

服务器端 Blazor 与 MVC [关闭]

Razor 页面与服务器端 Blazor

如何限制 Blazor 服务器端请求大小

如何从 Blazor 服务器端应用程序中的 Razor 页面导航到 Blazor 组件?