使用动态参数

时间:2019-06-20 20:01:01

标签: c# asp.net dictionary recursion dynamic

关于动态类型与jsRuntime调用相结合的问题,我一直陷入困境。

有一个实际问题:
如何使用动态对象作为参数从C#代码调用Javascript函数?
如果无法做到这一点,那么最好的方法是完全转换它,使其可以被InvokeAsync的{​​{1}}函数接受?

现在,到我已经尝试过的(显然失败了)。

我正在使用library from github which implements ChartJS in blazor。我复制了源代码而不是使用nuget软件包,因为在blazor或其他依赖项的最新更新中似乎已损坏某些内容。

我正在做的是从剃刀组件中调用Javascript函数,并且还为该函数传递了配置文件。 IJSRuntime方法将配置(实际类型)转换为动态类型,而没有所有为空的属性。

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; } 方法,则会出现以下错误:

  

System.NotSupportedException:'不支持集合类型'System.Dynamic.ExpandoObject'。

因此,经过一些研究,我坚持使用this answer,建议将动态对象转换为字典。
但是可悲的是,此代码发生了完全相同的错误:

InvokeAsync

然后我在调试检查器中看到,即使我创建了dynamic dynParam = StripNulls(chartConfig); Dictionary<string, object> param = new Dictionary<string, object>(dynParam); return jsRuntime.InvokeAsync<bool>("ChartJSInterop.SetupChart", param); 之后,在Dictionary中仍然有Dictionary个可能引起异常的事件。让我感到惊讶的是,这种固执并不是递归的。

因此,我创建了自己的递归函数,以将动态对象完全转换为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
我没有其他想法,希望有一些互联网陌生人可以帮助我。我的递归解决方案可能出了点问题,或者我忽略了一件小事,但是我还没有找到它。

其他信息:

版本:
最新预览中的所有内容(.net Core 3,VS 19,C#)

异常堆栈跟踪:

  在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(Type propertyType,PropertyInfo propertyInfo,Type classType,JsonSerializerOptions选项)      在System.Text.Json.Serialization.JsonClassInfo..ctor中(类型类型,JsonSerializerOptions选项)      在System.Text.Json.Serialization.JsonSerializerOptions.GetOrAddClass(Type classType)      在System.Text.Json.Serialization.JsonSerializer.GetRuntimeClassInfo(对象值,JsonClassInfo&jsonClassInfo,JsonSerializerOptions选项)      在System.Text.Json.Serialization.JsonSerializer.HandleEnumerable(JsonClassInfo elementClassInfo,JsonSerializerOptions选项,Utf8JsonWriter writer,WriteStack&state)中      在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](字符串标识符,Object []参数)      在ChartJs.Blazor.ChartJS.ChartJsInterop.SetupChart(IJSRuntime jsRuntime,ChartConfigBase chartConfig)

1 个答案:

答案 0 :(得分:2)

更新

我已将此功能放在CodeReview(see)上,并进行了一些改进。首先,介绍一些常规内容,然后在当前解决方案中出现一个致命错误IEnumerable<object>的处理是错误的。仅转换ExpandoObject很好,但是我完全忽略了ExpandoObject 以外的所有内容。在新解决方案中已修复此问题。
您可能想做的一件事就是将其转换为一种扩展方法,使其更加简洁,但在我的情况下,我不想这样做,因为我希望该函数是私有的。如果您是公开的,则应真正考虑扩展方法。

/// <summary>
/// This method is specifically used to convert an <see cref="ExpandoObject"/> with a Tree structure to a <see cref="Dictionary{string, object}"/>.
/// </summary>
/// <param name="expando">The <see cref="ExpandoObject"/> to convert</param>
/// <returns>The fully converted <see cref="ExpandoObject"/></returns>
private static Dictionary<string, object> ConvertExpandoObjectToDictionary(ExpandoObject expando) => RecursivelyConvertIDictToDict(expando);

/// <summary>
/// This method takes an <see cref="IDictionary{string, object}"/> and recursively converts it to a <see cref="Dictionary{string, object}"/>. 
/// The idea is that every <see cref="IDictionary{string, object}"/> in the tree will be of type <see cref="Dictionary{string, object}"/> instead of some other implementation like <see cref="ExpandoObject"/>.
/// </summary>
/// <param name="value">The <see cref="IDictionary{string, object}"/> to convert</param>
/// <returns>The fully converted <see cref="Dictionary{string, object}"/></returns>
private static Dictionary<string, object> RecursivelyConvertIDictToDict(IDictionary<string, object> value) =>
    value.ToDictionary(
        keySelector => keySelector.Key,
        elementSelector =>
        {
            // if it's another IDict just go through it recursively
            if (elementSelector.Value is IDictionary<string, object> dict)
            {
                return RecursivelyConvertIDictToDict(dict);
            }

            // if it's an IEnumerable check each element
            if (elementSelector.Value is IEnumerable<object> list)
            {
                // go through all objects in the list
                // if the object is an IDict -> convert it
                // if not keep it as is
                return list
                    .Select(o => o is IDictionary<string, object>
                        ? RecursivelyConvertIDictToDict((IDictionary<string, object>)o)
                        : o
                    );
            }

            // neither an IDict nor an IEnumerable -> it's fine to just return the value it has
            return elementSelector.Value;
        }
    );

原始答案

Soo我几个小时后终于找到了答案。问题是(有点意外)ConvertDynamicToDictionary方法。
我的递归解决方案仅检查是否还有另一个IDictionary,但是最终发生的是树上某处有一个ExpandoObject数组。在为IEnumerable添加了这张支票后,它起作用了,现在的方法如下:

private static Dictionary<string, object> ConvertDynamicToDictonary(IDictionary<string, object> value)
{
    return value.ToDictionary(
        p => p.Key,
        p =>
        {
            // if it's another IDict (might be a ExpandoObject or could also be an actual Dict containing ExpandoObjects) just go trough it recursively
            if (p.Value is IDictionary<string, object> dict)
            {
                return ConvertDynamicToDictonary(dict);
            }

            // if it's an IEnumerable, it might have ExpandoObjects inside, so check for that
            if (p.Value is IEnumerable<object> list)
            {
                if (list.Any(o => o is ExpandoObject))
                { 
                    // if it does contain ExpandoObjects, take all of those and also go trough them recursively
                    return list
                        .Where(o => o is ExpandoObject)
                        .Select(o => ConvertDynamicToDictonary((ExpandoObject)o));
                }
            }

            // neither an IDict nor an IEnumerable -> it's probably fine to just return the value it has
            return p.Value;
        } 
    );
}

我很高兴对此功能有任何批评,因为我不知道我是否已涵盖所有可能性。随时告诉我任何可以引起您注意的问题,可以改进。对于我来说绝对可以,所以这将是我对自己问题的回答。