admin管理员组

文章数量:1377579

This demo code showcases how the XSerializer 0.1.28 works but it's deprecated and new libraries do not allow Serializing an Array as a Sequence of Elements for multiple lists with the same serialized name in the same class or an inherited class (see )

See error at the bottom of the code.

Note this is a small demo to showcase the requirement. In the real use case inheritance is 10 levels deep with 100 fields so automated serialization would be preferred.

The solution would be a library that has the same behavior as the deprecated XSerializer, an XML Attribute or manual building of the XML -- not preferred due to the amount of classes and fields.

// app.csproj
    <Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>


  <ItemGroup>
    <!-- <PackageReference Include="RockLib.Serialization" Version="2.0.0" />
    <PackageReference Include="RockLib.Serialization.XSerializer" Version="1.0.5" /> -->
    <PackageReference Include="ExtendedXmlSerializer" Version="3.8.0" />
    <PackageReference Include="XSerializer" Version="0.1.28" />
  </ItemGroup>

</Project>
// Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using ExtendedXmlSerializer;

// old version works
using XSerializer;

// new version does not work
// using RockLib.Serialization;
// using RockLib.Serialization.XSerializer;

public class Board
{
   [XmlElement(IsNullable = true)]
   public virtual string Name { get; set; }
}

public class InputBoard : Board
{
   [XmlElement(IsNullable = true)]
   public override string Name { get; set; }
}

public class ExpansionBoard : Board
{
   [XmlElement(IsNullable = true)]
   public override string Name { get; set; }
   [XmlElement(ElementName = "BoardInput")] // THIS SAME NAME CAUSES ERROR
   public List<InputBoard> Inputs { get; set; }
}

public class MotherBoard : ExpansionBoard
{
   [XmlElement(IsNullable = true)]
   public override string Name { get; set; }

   [XmlElement(ElementName = "ExpansionBoard")]
   public List<ExpansionBoard> ExpansionBoards { get; set; }

   [XmlElement(ElementName = "BoardInput")] // THIS NAME CAUSES ERROR
   public List<InputBoard> USBInputs { get; set; }

   [XmlElement(ElementName = "BoardInput")] // THIS SAME NAME CAUSES ERROR
   public List<InputBoard> SerialInputs { get; set; }
}

public class MyXSerializer<T> where T : class
{
    public static string Serialize(T obj)
    {
      XSerializer.XmlSerializer<T> serializer = new XSerializer.XmlSerializer<T>();
      string xml = serializer.Serialize(obj);
      return xml;
    }
}

public class MyPrettyXSerializer<T> where T : class
{
    public static string Serialize(T obj)
    {
        XSerializer.XmlSerializer<T> serializer = new XSerializer.XmlSerializer<T>();
        string xml = serializer.Serialize(obj);

        // Load the XML string into an XmlDocument
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(xml);

        // Create an XmlWriterSettings object to specify formatting options
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.IndentChars = "\t";
        settings.NewLineChars = Environment.NewLine;
        settings.NewLineHandling = NewLineHandling.Replace;

        // Use the XmlWriterSettings object to write the XML to a string
        using (var sw = new StringWriter())
        {
            using (var writer = XmlWriter.Create(sw, settings))
            {
                xmlDoc.WriteTo(writer);
            }
            return sw.ToString();
        }
    }
}

// public class MyRockXSerializer<T> where T : class
// {
//     public static string Serialize(T obj)
//     {
//       var xml = obj.ToXml();
//       return xml;
//     }
// }

public class MySerializer<T> where T : class
{
    public static string Serialize(T obj)
    {
        System.Xml.Serialization.XmlSerializer xsSubmit = new System.Xml.Serialization.XmlSerializer(typeof(T));
        using (var sww = new StringWriter())
        {
            using (XmlTextWriter writer = new XmlTextWriter(sww) { Formatting = Formatting.Indented })
            {
                xsSubmit.Serialize(writer, obj);
                return sww.ToString();
            }
        }
    }
}

public class MyExtendedXmlSerializer<T>where T : class
{
   public static string Serialize(T obj)
    {
      ExtendedXmlSerializer.IExtendedXmlSerializer serializer = new ExtendedXmlSerializer.Configuration.ConfigurationContainer()
        .UseAutoFormatting()
        .UseOptimizedNamespaces()
        .EnableImplicitTyping(typeof(T))
        .Create();

      var xml = serializer.Serialize(new XmlWriterSettings { Indent = true }, obj);
      return xml.ToString();
    }
}

public class MyExtended2XmlSerializer<T>where T : class
{
   public static string Serialize(T obj)
    {
      ExtendedXmlSerializer.Configuration.IConfigurationContainer configuration = new ExtendedXmlSerializer.Configuration.ConfigurationContainer();
      var serializer = configuration.Create();
      using (var stringWriter = new StringWriter())
      {
            var xml = serializer.Serialize(obj);
            return xml;
      }
    }
}

public class Program
{
   public static void Main()
   {
      InputBoard ib1 = new InputBoard{Name = "NVidia 850i"};
      InputBoard ib2 = new InputBoard{Name = "NVidia M8"};
      InputBoard ib3 = new InputBoard{Name = "NVidia M5"};

      InputBoard usb1 = new InputBoard{Name = "USB KB"};
      InputBoard usb2 = new InputBoard{Name = "USB MU"};
      InputBoard usb3 = new InputBoard{Name = "USB AU"};

      InputBoard ser1 = new InputBoard{Name = "SER 1"};
      InputBoard ser2 = new InputBoard{Name = "SER 2"};
      InputBoard ser3 = new InputBoard{Name = "SER 3"};

      ExpansionBoard eb = new ExpansionBoard{ Name = "Nvidia Expander BRD", Inputs = new List<InputBoard>{ib1, ib2, ib3}};

      MotherBoard mb = new MotherBoard{Name = "MST 3000 MB", ExpansionBoards = new List<ExpansionBoard>{eb}, USBInputs = new List<InputBoard>{usb1, usb2, usb3}, SerialInputs = new List<InputBoard>{ser1, ser2, ser3}};

      string xml;
      
      // xml = MyXSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // same but pretty
      xml = MyPrettyXSerializer<MotherBoard>.Serialize(mb);
      Console.WriteLine(xml);

      // xml = MyRockXSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // xml = MySerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // xml = MyExtendedXmlSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // xml = MyExtended2XmlSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);
   }

}

The error with newer libraries


Unhandled exception. System.InvalidOperationException: There was an error reflecting type 'MotherBoard'.
 ---> System.InvalidOperationException: There was an error reflecting property 'USBInputs'.
 ---> System.InvalidOperationException: The XML element 'BoardInput' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.
   at System.Xml.Serialization.XmlReflectionImporter.AddUniqueAccessor(INameScope scope, Accessor accessor)
   at System.Xml.Serialization.XmlReflectionImporter.AddUniqueAccessor(MemberMapping member, INameScope elements, INameScope attributes, Boolean isSequence)
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportElement(TypeModel model, XmlRootAttribute root, String defaultNamespace, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)      
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at RockLib.Serialization.DefaultXmlSerializer.SerializeToString(Object item, Type type) in /_/RockLib.Serialization/DefaultXmlSerializer.cs:line 123
   at RockLib.Serialization.Serializer.ToXml(Object item, Type type, String name) in /_/RockLib.Serialization/Serializer.cs:line 124   
   at RockLib.Serialization.Serializer.ToXml[T](T item, String name) in /_/RockLib.Serialization/Serializer.cs:line 114
   at MyRockXSerializer`1.Serialize(T obj) in C:\code\xml3\app\Program.cs:line 91
   at Program.Main() in C:\code\xml3\app\Program.cs:line 169

This demo code showcases how the XSerializer 0.1.28 works but it's deprecated and new libraries do not allow Serializing an Array as a Sequence of Elements for multiple lists with the same serialized name in the same class or an inherited class (see https://learn.microsoft/en-us/dotnet/standard/serialization/controlling-xml-serialization-using-attributes )

See error at the bottom of the code.

Note this is a small demo to showcase the requirement. In the real use case inheritance is 10 levels deep with 100 fields so automated serialization would be preferred.

The solution would be a library that has the same behavior as the deprecated XSerializer, an XML Attribute or manual building of the XML -- not preferred due to the amount of classes and fields.

// app.csproj
    <Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>


  <ItemGroup>
    <!-- <PackageReference Include="RockLib.Serialization" Version="2.0.0" />
    <PackageReference Include="RockLib.Serialization.XSerializer" Version="1.0.5" /> -->
    <PackageReference Include="ExtendedXmlSerializer" Version="3.8.0" />
    <PackageReference Include="XSerializer" Version="0.1.28" />
  </ItemGroup>

</Project>
// Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using ExtendedXmlSerializer;

// old version works
using XSerializer;

// new version does not work
// using RockLib.Serialization;
// using RockLib.Serialization.XSerializer;

public class Board
{
   [XmlElement(IsNullable = true)]
   public virtual string Name { get; set; }
}

public class InputBoard : Board
{
   [XmlElement(IsNullable = true)]
   public override string Name { get; set; }
}

public class ExpansionBoard : Board
{
   [XmlElement(IsNullable = true)]
   public override string Name { get; set; }
   [XmlElement(ElementName = "BoardInput")] // THIS SAME NAME CAUSES ERROR
   public List<InputBoard> Inputs { get; set; }
}

public class MotherBoard : ExpansionBoard
{
   [XmlElement(IsNullable = true)]
   public override string Name { get; set; }

   [XmlElement(ElementName = "ExpansionBoard")]
   public List<ExpansionBoard> ExpansionBoards { get; set; }

   [XmlElement(ElementName = "BoardInput")] // THIS NAME CAUSES ERROR
   public List<InputBoard> USBInputs { get; set; }

   [XmlElement(ElementName = "BoardInput")] // THIS SAME NAME CAUSES ERROR
   public List<InputBoard> SerialInputs { get; set; }
}

public class MyXSerializer<T> where T : class
{
    public static string Serialize(T obj)
    {
      XSerializer.XmlSerializer<T> serializer = new XSerializer.XmlSerializer<T>();
      string xml = serializer.Serialize(obj);
      return xml;
    }
}

public class MyPrettyXSerializer<T> where T : class
{
    public static string Serialize(T obj)
    {
        XSerializer.XmlSerializer<T> serializer = new XSerializer.XmlSerializer<T>();
        string xml = serializer.Serialize(obj);

        // Load the XML string into an XmlDocument
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(xml);

        // Create an XmlWriterSettings object to specify formatting options
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.IndentChars = "\t";
        settings.NewLineChars = Environment.NewLine;
        settings.NewLineHandling = NewLineHandling.Replace;

        // Use the XmlWriterSettings object to write the XML to a string
        using (var sw = new StringWriter())
        {
            using (var writer = XmlWriter.Create(sw, settings))
            {
                xmlDoc.WriteTo(writer);
            }
            return sw.ToString();
        }
    }
}

// public class MyRockXSerializer<T> where T : class
// {
//     public static string Serialize(T obj)
//     {
//       var xml = obj.ToXml();
//       return xml;
//     }
// }

public class MySerializer<T> where T : class
{
    public static string Serialize(T obj)
    {
        System.Xml.Serialization.XmlSerializer xsSubmit = new System.Xml.Serialization.XmlSerializer(typeof(T));
        using (var sww = new StringWriter())
        {
            using (XmlTextWriter writer = new XmlTextWriter(sww) { Formatting = Formatting.Indented })
            {
                xsSubmit.Serialize(writer, obj);
                return sww.ToString();
            }
        }
    }
}

public class MyExtendedXmlSerializer<T>where T : class
{
   public static string Serialize(T obj)
    {
      ExtendedXmlSerializer.IExtendedXmlSerializer serializer = new ExtendedXmlSerializer.Configuration.ConfigurationContainer()
        .UseAutoFormatting()
        .UseOptimizedNamespaces()
        .EnableImplicitTyping(typeof(T))
        .Create();

      var xml = serializer.Serialize(new XmlWriterSettings { Indent = true }, obj);
      return xml.ToString();
    }
}

public class MyExtended2XmlSerializer<T>where T : class
{
   public static string Serialize(T obj)
    {
      ExtendedXmlSerializer.Configuration.IConfigurationContainer configuration = new ExtendedXmlSerializer.Configuration.ConfigurationContainer();
      var serializer = configuration.Create();
      using (var stringWriter = new StringWriter())
      {
            var xml = serializer.Serialize(obj);
            return xml;
      }
    }
}

public class Program
{
   public static void Main()
   {
      InputBoard ib1 = new InputBoard{Name = "NVidia 850i"};
      InputBoard ib2 = new InputBoard{Name = "NVidia M8"};
      InputBoard ib3 = new InputBoard{Name = "NVidia M5"};

      InputBoard usb1 = new InputBoard{Name = "USB KB"};
      InputBoard usb2 = new InputBoard{Name = "USB MU"};
      InputBoard usb3 = new InputBoard{Name = "USB AU"};

      InputBoard ser1 = new InputBoard{Name = "SER 1"};
      InputBoard ser2 = new InputBoard{Name = "SER 2"};
      InputBoard ser3 = new InputBoard{Name = "SER 3"};

      ExpansionBoard eb = new ExpansionBoard{ Name = "Nvidia Expander BRD", Inputs = new List<InputBoard>{ib1, ib2, ib3}};

      MotherBoard mb = new MotherBoard{Name = "MST 3000 MB", ExpansionBoards = new List<ExpansionBoard>{eb}, USBInputs = new List<InputBoard>{usb1, usb2, usb3}, SerialInputs = new List<InputBoard>{ser1, ser2, ser3}};

      string xml;
      
      // xml = MyXSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // same but pretty
      xml = MyPrettyXSerializer<MotherBoard>.Serialize(mb);
      Console.WriteLine(xml);

      // xml = MyRockXSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // xml = MySerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // xml = MyExtendedXmlSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);

      // xml = MyExtended2XmlSerializer<MotherBoard>.Serialize(mb);
      // Console.WriteLine(xml);
   }

}

The error with newer libraries


Unhandled exception. System.InvalidOperationException: There was an error reflecting type 'MotherBoard'.
 ---> System.InvalidOperationException: There was an error reflecting property 'USBInputs'.
 ---> System.InvalidOperationException: The XML element 'BoardInput' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.
   at System.Xml.Serialization.XmlReflectionImporter.AddUniqueAccessor(INameScope scope, Accessor accessor)
   at System.Xml.Serialization.XmlReflectionImporter.AddUniqueAccessor(MemberMapping member, INameScope elements, INameScope attributes, Boolean isSequence)
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportElement(TypeModel model, XmlRootAttribute root, String defaultNamespace, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)      
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at RockLib.Serialization.DefaultXmlSerializer.SerializeToString(Object item, Type type) in /_/RockLib.Serialization/DefaultXmlSerializer.cs:line 123
   at RockLib.Serialization.Serializer.ToXml(Object item, Type type, String name) in /_/RockLib.Serialization/Serializer.cs:line 124   
   at RockLib.Serialization.Serializer.ToXml[T](T item, String name) in /_/RockLib.Serialization/Serializer.cs:line 114
   at MyRockXSerializer`1.Serialize(T obj) in C:\code\xml3\app\Program.cs:line 91
   at Program.Main() in C:\code\xml3\app\Program.cs:line 169
Share Improve this question asked Mar 20 at 18:22 Adam MendozaAdam Mendoza 5,9152 gold badges27 silver badges32 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

I could use the default System.Xml.Serialization.XmlSerializer to serialize your XML. The "fix" is to add the Order-attribute on all fields.

By not setting the Order the serializer finds element BoardInput and has no idea to which object it has to connect it to.

I updated your code to this and that gave me a working console-application:

public class Board
{
    [XmlElement(IsNullable = true, Order =1)]
    public virtual string Name { get; set; }
}

public class InputBoard : Board
{
    [XmlElement(IsNullable = true, Order=1)]
    public override string Name { get; set; }
}

public class ExpansionBoard : Board
{
    [XmlElement(IsNullable = true, Order =1)]
    public override string Name { get; set; }
    [XmlElement(ElementName = "BoardInput", Order=2)] // THIS SAME NAME CAUSES ERROR
    public List<InputBoard> Inputs { get; set; }
}

public class MotherBoard : ExpansionBoard
{
    [XmlElement(IsNullable = true, Order =1)]
    public override string Name { get; set; }

    [XmlElement(ElementName = "ExpansionBoard", Order =2)]
    public List<ExpansionBoard> ExpansionBoards { get; set; }

    [XmlElement(ElementName = "BoardInput", Order=3)] // THIS NAME CAUSES ERROR
    public List<InputBoard> USBInputs { get; set; }

    [XmlElement(ElementName = "BoardInput", Order=4)] // THIS SAME NAME CAUSES ERROR
    public List<InputBoard> SerialInputs { get; set; }
}

public class MyPrettyXSerializer<T> where T : class
{
    public static string Serialize(T obj)
    {
        var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
        var xml = string.Empty;
        using (var memoryStream = new MemoryStream())
        {
            serializer.Serialize(memoryStream, obj);
            memoryStream.Position = 0;
            var streamReader = new StreamReader(memoryStream);
            xml = streamReader.ReadToEnd();
            streamReader.Close();
        }

        // Load the XML string into an XmlDocument
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(xml);

        // Create an XmlWriterSettings object to specify formatting options
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.IndentChars = "\t";
        settings.NewLineChars = Environment.NewLine;
        settings.NewLineHandling = NewLineHandling.Replace;

        // Use the XmlWriterSettings object to write the XML to a string
        using (var sw = new StringWriter())
        {
            using (var writer = XmlWriter.Create(sw, settings))
            {
                xmlDoc.WriteTo(writer);
            }
            return sw.ToString();
        }
    }
}

本文标签: