我一直被困在一个关于动态类型与 jsRuntime 调用相结合的问题上。
有一个实际的问题:
如何使用动态对象作为参数从 C# 代码调用 Javascript 函数?
如果这是不可能的,那么完全转换它以便它可以被 a 的InvokeAsync函数接受的最佳方法是IJSRuntime什么?
现在,我已经尝试过(显然失败了)。
我正在使用来自 github 的库,它在 blazor 中实现 ChartJS。我已经复制了源代码而不是使用 nuget 包,因为在 blazor 或其他一些依赖项的最后更新中似乎有一些东西被破坏了。
我正在做的是从我的 razor 组件调用一个 Javascript 函数,并且我还在为所述函数传递我的配置。该StripNulls方法将配置(实际类型)转换为动态类型,而没有所有为空的属性。
dynamic param = StripNulls(chartConfig);
return jsRuntime.InvokeAsync<bool>("ChartJSInterop.SetupChart", param);
我认为没有必要为该StripNulls方法放置代码,但也许我遗漏了一些重要的东西,所以这里是代码。
/// Returns an object that is equivalent to the given parameter but without any null member AND it preserves DotNetInstanceClickHandler/DotNetInstanceHoverHandler members intact
///
/// <para>Preserving DotNetInstanceClick/HoverHandler members is important because they contain DotNetObjectRefs to the instance whose method should be invoked on click/hover</para>
///
/// <para>This whole method is hacky af but necessary. Stripping null members is only needed because the default config for the Line charts on the Blazor side is somehow messed up. If this were not the case no null member stripping were necessary and hence, the recovery of the DotNetObjectRef members would also not be needed. Nevertheless, The Show must go on!</para>
/// </summary>
/// <param name="chartConfig"></param>
/// <returns></returns>
private static ExpandoObject StripNulls(ChartConfigBase chartConfig)
{
// Serializing with the custom serializer settings remove null members
var cleanChartConfigStr = JsonConvert.SerializeObject(chartConfig, JsonSerializerSettings);
// Get back an ExpandoObject dynamic with the clean config - having an ExpandoObject allows us to add/replace members regardless of type
dynamic clearConfigExpando = JsonConvert.DeserializeObject<ExpandoObject>(cleanChartConfigStr, new ExpandoObjectConverter());
// Restore any .net refs that need to be passed intact
var dynamicChartConfig = (dynamic) chartConfig;
if (dynamicChartConfig?.Options?.Legend?.OnClick != null
&& dynamicChartConfig?.Options?.Legend?.OnClick is DotNetInstanceClickHandler)
{
clearConfigExpando.options = clearConfigExpando.options ?? new { };
clearConfigExpando.options.legend = clearConfigExpando.options.legend ?? new { };
clearConfigExpando.options.legend.onClick = dynamicChartConfig.Options.Legend.OnClick;
}
if (dynamicChartConfig?.Options?.Legend?.OnHover != null
&& dynamicChartConfig?.Options?.Legend?.OnHover is DotNetInstanceHoverHandler)
{
clearConfigExpando.options = clearConfigExpando.options ?? new { };
clearConfigExpando.options.legend = clearConfigExpando.options.legend ?? new { };
clearConfigExpando.options.legend.onHover = dynamicChartConfig.Options.Legend.OnHover;
}
return clearConfigExpando;
}
但是,如果我尝试InvokeAsync使用此动态对象调用该方法,则会收到以下错误:
System.NotSupportedException:'不支持集合类型'System.Dynamic.ExpandoObject'。'
因此,经过一些研究,我偶然发现了这个建议将动态对象转换为字典的答案。
但遗憾的是,这段代码出现了完全相同的错误:
dynamic dynParam = StripNulls(chartConfig);
Dictionary<string, object> param = new Dictionary<string, object>(dynParam);
return jsRuntime.InvokeAsync<bool>("ChartJSInterop.SetupChart", param);
然后我在调试检查器中看到,即使在我创建了 a 之后,字典Dictionary中仍然有ExpandoObjects 可能导致异常。令我惊讶的是,这种转换不是递归的。
所以我创建了自己的递归函数来将动态对象完全转换为字典。我是这样实现的,它似乎可以工作(它是一个非常大的嵌套对象,但我查看的所有属性都很好):
private static Dictionary<string, object> ConvertDynamicToDictonary(IDictionary<string, object> value)
{
return value.ToDictionary(
p => p.Key,
p =>
p.Value is IDictionary<string, object>
? ConvertDynamicToDictonary((IDictionary<string, object>)p.Value)
: p.Value
);
}
并像这样调用(不,我不只是不小心传入了错误的参数):
dynamic dynParam = StripNulls(chartConfig);
Dictionary<string, object> param = ConvertDynamicToDictonary(dynParam);
return jsRuntime.InvokeAsync<bool>("ChartJSInterop.SetupChart", param);
这仍然会引发完全相同的异常,现在我非常沮丧,不知道为什么它仍然告诉我ExpandoObject在我的脑海中什么时候我看不到它可能没有完全转换为Dictionary<string, object>.
我没有进一步的想法,并希望某种互联网上的陌生人可以帮助我解决这个问题。也许我的递归解决方案有问题,或者我忽略了一件小事,但我还没有设法找到它。
附加信息:
版本:
最新预览版中的所有内容(.net Core 3、VS 19、C#)
异常堆栈跟踪:
在 System.Text.Json.Serialization.JsonClassInfo.GetElementType(类型 propertyType,类型 parentType,MemberInfo memberInfo)在 System.Text.Json.Serialization.JsonClassInfo.CreateProperty(类型声明PropertyType,类型 runtimePropertyType,PropertyInfo propertyInfo,类型 parentClassType,JsonSerializerOptions 选项) System.Text.Json.Serialization.JsonClassInfo.AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) at System.Text.Json.Serialization.JsonClassInfo..ctor(Type type, JsonSerializerOptions options) Json.Serialization.JsonSerializerOptions.GetOrAddClass(Type classType) 在 System.Text.Json.Serialization.JsonSerializer.GetRuntimeClassInfo(Object value, JsonClassInfo& jsonClassInfo, JsonSerializerOptions options) 在 System.Text.Json。System.Text.Json.Serialization.JsonSerializer.Write(Utf8JsonWriter writer, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state) at System.Text.Json 的 Serialization.JsonSerializer.HandleEnumerable(JsonClassInfo elementClassInfo, JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state) System.Text.Json.Serialization.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options) 在 System.Text.Json.Serialization.JsonSerializer 的 Serialization.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options) .ToString[TValue](TValue value, JsonSerializerOptions options) at Microsoft.JSInterop.JSRuntimeBase.InvokeAsync[T](String identifier, Object[] args) at ChartJs.Blazor.ChartJS.ChartJsInterop。SetupChart(IJSRuntime jsRuntime, ChartConfigBase chartConfig)