如何在PowerShell中将XML映射到对象的动态属性?

时间:2011-11-22 22:18:09

标签: xml reflection powershell mapping

我需要自动将许多.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值动态访问和设置属性?

2 个答案:

答案 0 :(得分:2)

使用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和空的被视为虚假,其他一切都是真实的。这意味着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,但我可以看到有人想要显式初始化,或者通过更改值而不是删除条目来切换单个赋值,并运行此意外行为。)

答案 1 :(得分:0)

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

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