繁体   English   中英

使用动态参数从C#调用javascript方法

[英]Invoke javascript method from c# with dynamic parameter

我一直在与动态类型结合jsRuntime调用有关的问题上陷入困境。

有一个实际的问题:
如何使用动态对象作为参数从C#代码调用Javascript函数?
如果这是不可能的,什么是完全转换,以便它可以被接受的最佳方式InvokeAsync一个功能IJSRuntime

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

我正在使用来自github库,该库在blazor中实现了ChartJS 我复制了源代码而不是使用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方法, InvokeAsync出现以下错误:

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

因此,在进行了一些研究之后,我对这个答案 tum之以鼻,建议将动态对象转换为字典。
但是可悲的是,此代码发生了完全相同的错误:

dynamic dynParam = StripNulls(chartConfig);
Dictionary<string, object> param = new Dictionary<string, object>(dynParam);
return jsRuntime.InvokeAsync<bool>("ChartJSInterop.SetupChart", param);

然后,我在debug-inspector中看到,即使创建了Dictionary之后, Dictionary中仍然存在ExpandoObject ,这很可能导致异常。 让我感到惊讶的是,这种固执并不是递归的。

因此,我创建了自己的递归函数,以将动态对象完全转换为Dictionary。 我像这样实现它,并且似乎可以正常工作(这是一个很大的嵌套对象,但是我看过的所有属性都很好):

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);

仍然会引发完全相同的异常,现在我非常沮丧,不知道为什么为什么在我不知道如何将其完全转换为Dictionary<string, object>时仍然告诉我有关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(Type definedPropertyType,类型runtimePropertyType,PropertyInfo propertyInfo,类型parentClassType,JsonSerializerOptions选项)在System.Text.System.Text.Json.Serialization.JsonClassInfo..ctor(Type type,JsonSerializerOptions options)的System.Text.Json.Serialization.JsonClassInfo.AddProperty(Type propertyType,PropertyInfo propertyInfo,Type classType,JsonSerializerOptions options)处。 System.Text.Json.Serialization.JsonSerializer.GetRuntimeClassInfo(Object value,JsonClassInfo&jsonClassInfo,JsonSerializerOptions options)处的System.Text.Json.Serialization.JsonSerializerable(HandleEnumizer。选项,Utf8JsonWriter作家,WriteStack和状态),位于System.Text.Json.Serializatio n.JsonSerializer.Write(Utf8JsonWriter编写器,Int32 flushThreshold,JsonSerializerOptions选项,WriteStack和状态)位于System.Text.Json.Serialization.JsonSerializer.WriteCore(PooledByteBufferWriter输出,对象值,类型类型,JsonSerializerOptions选项。)。 Microsoft.JSInterop.JSRuntimeBase.InvokeAsync [T](String)处的Serialization.JsonSerializer.WriteCoreString(对象值,类型,JsonSerializerOptions选项)在System.Text.Json.Serialization.JsonSerializer.ToString [TValue](TValue值,JsonSerializerOptions选项)标识符,位于ChartJs.Blazor.ChartJS.ChartJsInterop.SetupChart(IJSRuntime jsRuntime,ChartConfigBase chartConfig)的Object [] args)

更新

我将此功能放在了CodeReview上( 请参阅参考资料),并且进行了一些改进。 首先是一些一般性的东西,但是在当前解决方案中有一个致命的错误 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;
        }
    );

原始答案

这么长时间后,我终于找到了答案。 问题是(有点ConvertDynamicToDictionaryConvertDynamicToDictionary方法。
我的递归解决方案仅检查是否还有另一个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;
        } 
    );
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM