【问题标题】:Can a DynamicParameter rely on the values of other DynamicParameters?DynamicParameter 可以依赖其他 DynamicParameters 的值吗?
【发布时间】:2015-06-11 19:04:19
【问题描述】:

我在 cmdlet Get-DateSlain 上有一个 GetDynamicParameters(),它执行以下操作:

    public object GetDynamicParameters()
    {
        List<string> houseList = {"Stark", "Lannister", "Tully"};

        var attributes = new Collection<Attribute>
            {
                new ParameterAttribute
                {
                    HelpMessage = "Enter a house name",
                },
                new ValidateSetAttribute(houseList.ToArray()),
            };

        if (!this.ContainsKey("House"))
        {
            this.runtimeParameters.Add("House", new RuntimeDefinedParameter("House", typeof(string), attributes));
        }
    }

这正如预期的那样工作——用户可以输入Get-DateSlain -House,然后在可用的房屋中进行选项卡。但是,一旦选择了房子,我希望能够将结果缩小到该房子中的角色。此外,如果它是房子“斯塔克”,我想允许一个-Wolf 参数。因此要实现(为简洁起见,删除了一些值有效性检查):

    public object GetDynamicParameters()
    {
        if (this.runtimeParameters.ContainsKey("House"))
        {
            // We already have this key - no need to re-add. However, now we can add other parameters
            var house = this.runtimeParameters["House"].Value.ToString();
            if (house == "Stark")
            {
                List<string> characters = { "Ned", "Arya", "Rob" };
                var attributes = new Collection<Attribute>
                {
                    new ParameterAttribute
                    {
                        HelpMessage = "Enter a character name",
                    },
                    new ValidateSetAttribute(characters.ToArray()),
                };

                this.runtimeParameters.Add("Character", new RuntimeDefinedParameter("Character", typeof(string), attributes));

                List<string> wolves = { "Shaggydog", "Snow", "Lady" };
                var attributes = new Collection<Attribute>
                {
                    new ParameterAttribute
                    {
                        HelpMessage = "Enter a wolf name",
                    },
                    new ValidateSetAttribute(wolves.ToArray()),
                };

                this.runtimeParameters.Add("Wolf", new RuntimeDefinedParameter("Wolf", typeof(string), attributes));
            }
            else if (house == "Lannister")
            {
                List<string> characters = { "Jaimie", "Cersei", "Tywin" };
                // ...
            }
            // ...

            return this.runtimeParameters;
        }

        List<string> houseList = {"Stark", "Lannister", "Tully"};

        var attributes = new Collection<Attribute>
        {
            new ParameterAttribute
            {
                HelpMessage = "Enter a house name",
            },
            new ValidateSetAttribute(houseList.ToArray()),
        };

        this.runtimeParameters.Add("House", new         RuntimeDefinedParameter("House", typeof(string), attributes));
    }

这看起来应该可以工作,但事实并非如此。 GetDynamicParameters 函数仅被调用一次,即在向this.runtimeParameters["House"] 提供值之前。由于在填写该值后它不会重新评估,因此永远不会添加额外的字段,并且ProcessRecord 中依赖这些字段的任何逻辑都将失败。

那么 - 有没有办法让多个相互依赖的动态参数?

【问题讨论】:

    标签: c# powershell cmdlets


    【解决方案1】:

    看看这个问题的答案,它显示了一种在GetDynamicParameters 方法中访问其他动态参数值的方法: Powershell module: Dynamic mandatory hierarchical parameters

    我改编了上述答案中的代码,以便它可以处理 SwitchParameters 并将原始输入参数转换为 cmdlet 参数的实际类型。如果您要获取其值的动态参数是通过管道传递的,则它不起作用。我认为这是不可能的,因为动态参数总是在评估管道输入之前创建。这里是:

    public static class DynamicParameterExtension
    {
        public static T GetUnboundValue<T>(this PSCmdlet cmdlet, string paramName, int unnamedPosition = -1))
        {
            var context = TryGetProperty(cmdlet, "Context");
            var processor = TryGetProperty(context, "CurrentCommandProcessor");
            var parameterBinder = TryGetProperty(processor, "CmdletParameterBinderController");
            var args = TryGetProperty(parameterBinder, "UnboundArguments") as System.Collections.IEnumerable;
    
            if (args != null)
            {
                var isSwitch = typeof(SwitchParameter) == typeof(T);
    
                var currentParameterName = string.Empty;
                object unnamedValue = null;
                var i = 0;
                foreach (var arg in args)
                {
                    var isParameterName = TryGetProperty(arg, "ParameterNameSpecified");
                    if (isParameterName != null && true.Equals(isParameterName))
                    {
                        var parameterName = TryGetProperty(arg, "ParameterName") as string;
                        currentParameterName = parameterName;
                        if (isSwitch && string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
                        {
                            return (T)(object)new SwitchParameter(true);
                        }
                        continue;
                    }
    
                    var parameterValue = TryGetProperty(arg, "ArgumentValue");
    
                    if (currentParameterName != string.Empty)
                    {
                        if (string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
                        {
                            return ConvertParameter<T>(parameterValue);
                        }
                    }
                    else if (i++ == unnamedPosition)
                    {
                        unnamedValue = parameterValue;
                    }
    
                    currentParameterName = string.Empty;
                }
    
                if (unnamedValue != null)
                {
                    return ConvertParameter<T>(unnamedValue);
                }
            }
    
            return default(T);
        }
    
        static T ConvertParameter<T>(this object value)
        {
            if (value == null || Equals(value, default(T)))
            {
                return default(T);
            }
    
            var psObject = value as PSObject;
            if (psObject != null)
            {
                return psObject.BaseObject.ConvertParameter<T>();
            }
    
            if (value is T)
            {
                return (T)value;
            }
            var constructorInfo = typeof(T).GetConstructor(new[] { value.GetType() });
            if (constructorInfo != null)
            {
                return (T)constructorInfo.Invoke(new[] { value });
            }
    
            try
            {
                return (T)Convert.ChangeType(value, typeof(T));
            }
            catch (Exception)
            {
                return default(T);
            }
        }
    
        static object TryGetProperty(object instance, string fieldName)
        {
            if (instance == null || string.IsNullOrEmpty(fieldName))
            {
                return null;
            }
    
            const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
            var propertyInfo = instance.GetType().GetProperty(fieldName, bindingFlags);
    
            try
            {
                if (propertyInfo != null)
                {
                    return propertyInfo.GetValue(instance, null);
                }
                var fieldInfo = instance.GetType().GetField(fieldName, bindingFlags);
    
                return fieldInfo?.GetValue(instance);
            }
            catch (Exception)
            {
                return null;
            }
        }        
    }
    

    因此,对于您的示例,您应该能够像这样使用它:

    public object GetDynamicParameters()
    {
        var houseList = new List<string> { "Stark", "Lannister", "Tully" };
    
        var attributes = new Collection<Attribute>
        {
            new ParameterAttribute { HelpMessage = "Enter a house name" },
            new ValidateSetAttribute(houseList.ToArray()),
        };
        var runtimeParameters = new RuntimeDefinedParameterDictionary
        {
            {"House", new RuntimeDefinedParameter("House", typeof (string), attributes)}
        };
    
        var selectedHouse = this.GetUnboundValue<string>("House");
    
        //... add parameters dependant on value of selectedHouse
    
        return runtimeParameters;
    }
    

    毕竟,我不确定首先尝试获取这些动态参数值是否是个好主意。 PowerShell Cmdlet API 显然不支持它(请参阅 GetUnboundValue 方法中访问私有成员的所有反射),您必须重新实现 PowerShell 参数转换魔术(请参阅 ConvertParameter,我确定我错过了一些案例)并且有流水线值的限制。使用风险自负:)

    【讨论】:

    • 如果可能的话,请复制相关的代码部分
    • 我添加了一个修改版本的代码,它只获取未绑定的参数值。不幸的是,它并没有变得更短,但我认为它可以解决问题。
    猜你喜欢
    • 1970-01-01
    • 2013-07-03
    • 2014-11-15
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    • 2022-01-18
    • 1970-01-01
    相关资源
    最近更新 更多