【问题标题】:How to map XML to dynamic properties on an object in PowerShell?如何将 XML 映射到 PowerShell 中对象的动态属性?
【发布时间】:2023-04-02 02:15:01
【问题描述】:

我需要自动将许多 .NET 对象添加到业务系统中。 PowerShell 脚本需要读取 XML 输入文件并通过业务系统的 API 执行适当的更改。

我发现的问题是对象有许多不同的类型,因此具有不同的属性

这是一个 XML 示例:

<BusinessObject>
    <Action>Add</Action>
    <Id>{867B6C43-2A20-485D-A3E3-CBFCD50CA6F3}</Id>
    <AssemblyName>ABC.BusinessObjects, Version=1.0.0.0, Culture=neutral, PublicKeyToken=361ad75badc53918</AssemblyName>
    <ClassName>ABC.BusinessObjects.HealthService</ClassName>
    <!-- Properties specific to object -->
    <HealthServiceName>Jo's GP Super Center</HealthServiceName>
</BusinessObject>
<BusinessObject>
    <Action>Add</Action>
    <Id>{867B6C43-2A20-485D-A3E3-CBFCD50CA6F3}</Id>
    <AssemblyName>ABC.BusinessObjects, Version=1.0.0.0, Culture=neutral, PublicKeyToken=361ad75badc53918</AssemblyName>
    <ClassName>ABC.BusinessObjects.Patient</ClassName>
    <!-- Properties specific to object -->
    <PatientName>Anna Smith</PatientName>
</BusinessObject>

脚本的相关部分:

$xmlItem.BusinessObjects.GetElementsByTagName("BusinessObject") | % {
    $businessObject = $_
    if ($businessObject.Action -eq "Add") {
        $assemblyName = $businessObject.AssemblyName
        $className = $businessObject.ClassName
        $assembly = [Reflection.Assembly]::Load($assemblyName)
        $obj = $assembly.CreateInstance($className)
        ### TODO: How to set properties on $obj ???
        $api.AddBusinessObject($obj)
    }
}

我可能需要将特定于对象的属性放入它们自己的 XML 元素中,以便循环遍历它们。我不确定在该循环中要做什么。

假设每个属性的 XML 元素名称与 $obj 属性名称匹配,我如何从其对应的 XML 值动态访问和设置属性?

【问题讨论】:

    标签: xml reflection powershell mapping


    【解决方案1】:

    Your approach 使用PropertyInfo.SetValue 肯定会工作,但您也可以利用 PowerShell 的动态特性和自动类型转换来大大简化初始化:

    $businessObject.Properties.GetElementsByTagName( 'Property' ) | % {
      $obj.($_.Name) = $_.Value
    }
    

    在这里,PowerShell 将评估 ($_.Name) 值,然后使用该值调用 $obj.___,就像您编写了 $obj.SomeProperty 一样。它还将$_.Value返回的字符串转换为适当的类型(如下所示),而SetValue会抛出参数异常。


    结合您现有的代码:

    $xmlItem.BusinessObjects.GetElementsByTagName( 'BusinessObject' ) | % {
      $businessObject = $_
      if( $businessObject.Action -eq 'Add' ) {
        $assemblyName = $businessObject.AssemblyName
        $className = $businessObject.ClassName
        $assembly = [Reflection.Assembly]::Load( $assemblyName )
        $obj = $assembly.CreateInstance( $className )
    
        if( $businessObject.Properties ) {
          $businessObject.Properties.GetElementsByTagName( 'Property' ) | % {
            $obj.($_.Name) = $_.Value
          }
        }
    
        $obj  # $api.AddBusinessObject( $obj )
      }
    }
    

    我使用以下 xml 进行了测试,改编自问题的示例(AppDomainSetup 是一种方便的系统类型,具有许多可设置的属性):

    <BusinessObjects>
      <BusinessObject>
        <Action>Add</Action>
        <Id>{867B6C43-2A20-485D-A3E3-CBFCD50CA6F3}</Id>
        <AssemblyName>mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</AssemblyName>
        <ClassName>System.AppDomainSetup</ClassName>
        <Properties>
          <Property Name="DisallowCodeDownload" Value="True"/>
          <Property Name="LoaderOptimization" Value="SingleDomain"/>
          <Property Name="ConfigurationFile" Value="C:\config.xml"/>
        </Properties>
      </BusinessObject>
    </BusinessObjects>
    

    这会将布尔 DisallowCodeDownload、枚举 LoaderOptimization 和字符串 ConfigurationFile 属性设置为指定的非默认值。


    ∗ 注意: 对简单方法的一个警告是,普通的 PowerShell 脚本规则用于转换布尔值,因此只有 $null0 和空被视为 false,其他一切都是真实的。这意味着 xml 中的 Value="False" 设置仍然将属性设置为 true,因为 PowerShell 看到一个非空字符串 'False' 并且不会进一步解析它。

    可以使用空字符串Value="" 将属性设置为false。但是,为了实现更直观的解析行为,您需要手动调用 [Convert]::ToBoolean( $_.Value )[Convert]::ChangeType( $_.Value, [bool] ) 或类似名称:

    $name = $_.Name
    if( $obj.$name -is [bool] ) {
        $obj.$name = [Convert]::ToBoolean( $_.Value )
    } else {
        $obj.$name = $_.Value
    }
    

    (布尔属性的默认值通常为 false,但我可以看到有人想要显式初始化,或者通过更改值而不是删除条目来切换单个赋值,并遇到这种意外行为。)

    【讨论】:

      【解决方案2】:

      我在 XML 中添加了一个带有 Property 子节点的 Properties 元素,并添加了这个脚本:

      if ($businessObject.Properties) {
          $businessObject.Properties.GetElementsByTagName("Property") | % {
              $prop = $_
              $pi = $obj.GetType().GetProperty($prop.Name)
              $pi.SetValue($obj, $prop.Value, $null)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-16
        • 2022-01-01
        • 2017-03-16
        • 2015-06-05
        • 1970-01-01
        • 2010-09-10
        • 2013-10-16
        • 2021-04-13
        相关资源
        最近更新 更多