Я застрял в проблеме с динамическими типами в сочетании с вызовом jsRuntime.
Чтобы иметь актуальный вопрос:
Как я могу вызвать функцию Javascript из кода C # с динамическим объектом в качестве параметра?
Если это невозможно, каков наилучший способ полностью преобразовать его, чтобы его можно было принять с помощью функции InvokeAsync
IJSRuntime
?
Теперь, к тому, что я уже попробовал (и, очевидно, не удалось).
Я использую библиотеку от github, которая реализует ChartJS в Blazor . Я скопировал исходный код вместо использования пакета nuget, поскольку в последних обновлениях от Blazor или какой-либо другой зависимости что-то, похоже, было повреждено.
Что я делаю, так это то, что я вызываю функцию 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);
Затем я увидел в инспекторе отладки, что даже после создания Dictionary
в Словаре все еще оставалось ExpandoObject
, что, вероятно, вызвало исключение. Меня довольно удивило, что это убеждение не было рекурсивным.
Итак, я создал собственную рекурсивную функцию для полного преобразования динамического объекта в словарь. Я реализовал это так, и это, похоже, работало (это очень большой вложенный объект, но все свойства, на которые я смотрел, были в порядке):
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 #)
Трассировка стека исключений:
at 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 (тип propertyType, PropertyInfo propertyInfo, тип 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, WriteStack и состояние)
в System.Text.Json.Serialization.JsonSerializer.Write (модуль записи Utf8JsonWriter, Int32 flushThreshold, параметры JsonSerializerOptions, WriteStack & state)в System.Text.Json.Serialization.JsonSerializer.WriteCore (выход PooledByteBufferWriter, значение объекта, тип Type, параметры JsonSerializerOptions) в System.Text.Json.Serialization.JsonSerializer.WriteCoreString (значение объекта, тип Type, параметры JsonSerializerOptions)Text.Json.Serialization.JsonSerializer.IJSRuntime jsRuntime, ChartConfigBase chartConfig)