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

发布于 2024-12-17 07:38:46 字数 1731 浏览 4 评论 0原文

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

I need to automate the addition of many .NET objects to a business system. A PowerShell script needs to read an XML input file and perform the appropriate changes via the business system's API.

The problem I'm finding is that the objects are of many different types and therefore have different properties.

Here's an example of the 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>

Relevant portion of script:

$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)
    }
}

I probably need to put the object-specific properties into their own XML element so that I can loop through them. What I'm not sure about is what to do inside that loop.

Assuming each property's XML element name matches the $obj property name, how do I dynamically access and set the property from its corresponding XML value?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夏末 2024-12-24 07:38:46

您的方法使用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 对此进行了测试,该 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 ),或 [转换]::ChangeType( $_.Value, [ bool] ),或类似的东西:(

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

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

Your approach using PropertyInfo.SetValue will certainly work, but you can also take advantage of PowerShell's dynamic nature and automatic type conversion to greatly simplify the initialization:

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

Here, PowerShell will evaluate the ($_.Name) value, and then invoke $obj.___ with that value, just as if you had written $obj.SomeProperty. It will also convert the string returned by $_.Value to the appropriate type (as shown below), whereas SetValue would throw an argument exception.


Combining with your existing code:

$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 )
  }
}

I tested this using the following xml, adapted from the question's example (AppDomainSetup being a convenient system type with a lot of settable properties):

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

This sets the boolean DisallowCodeDownload, the enum LoaderOptimization, and the string ConfigurationFile properties to the specified non-default values.


∗ Note: One caveat to the simple approach is that the normal PowerShell scripting rules are used to convert boolean values, so only $null, 0, and empty are treated as false, and everything else as true. This means a Value="False" setting in xml will still set the property to true, because PowerShell sees a non-empty string 'False' and does not parse it further.

You can use an empty string, Value="", to set a property to false. However, to allow the more intuitive parsing behavior, you would need to manually call [Convert]::ToBoolean( $_.Value ), or [Convert]::ChangeType( $_.Value, [bool] ), or something similar:

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

(Boolean properties usually have a default value of false, but I could see someone wanting explicit initialization, or toggling a single assignment by changing the value instead of deleting the entry, and running across this unexpected behavior.)

っ左 2024-12-24 07:38:46

我向 XML 添加了一个带有 Property 子节点的 Properties 元素,并添加了以下脚本:

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

I added a Properties element with Property child nodes to the XML and added this script:

if ($businessObject.Properties) {
    $businessObject.Properties.GetElementsByTagName("Property") | % {
        $prop = $_
        $pi = $obj.GetType().GetProperty($prop.Name)
        $pi.SetValue($obj, $prop.Value, $null)
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文