1

I need to create a lot of XML files. They are very similar and they need to have the following schema:

<Servidor>
  <VersaoLayout>0001</VersaoLayout> <!--Will not change-->
  <CodigoUJ>001001</CodigoUJ>       <!--Will not change-->
    <ItemServidor>                  <!--A list of <T> that will change-->
      <CPFServidor>13579024681</CPFServidor>
      <NomeServidor>Fulano Pereira Tal</NomeServidor>
    </ItemServidor>
</Servidor>

Note that the tags <VersaoLayout> and <CodigoUJ> are the same for all the files i need to create, the <ItemServidor> is the content that will vary. So i thought: Why not create just one class and use generics to handle the differences for this case? And for now my classes are the following:

//Kind of a container class that will hold the content that will vary.
[XmlRoot("Servidor", ElementName="Servidor")]
public class LayoutArquivoXML<T> where T : ItemLayout
{
    [XmlElement("VersaoLayout")]
    public string VersaoLayout { get; set; }

    [XmlElement("CodigoUJ")]
    public string CodigoUJ { get; set; }

    //[XmlArrayItem("ItemServidor")]
    public List<T> ItensLayout { get; set; }

    // Constructors omited for simplicity
}

//A "ContentClass". I will have a bunch of classes similar to this
[XmlRoot("ItemServidor", ElementName = "ItemServidor")]
public class ServidorLayout : ItemLayout  
{
    [XmlElement("CPFServidor")]
    public string CPFServidor { get; set; }

    [XmlElement("NomeServidor")]
    public string Nome { get; set; }
}

I am instantiating my "container class" this way:

LayoutArquivoXML<ServidorLayout> layout = new Layout.LayoutArquivoXML<ServidorLayout>("versao01", "999", lstItensLayout.ToList());

And my Serialization method is the following:

 public string Serializador<T>(T objeto)
 {
     string xml = string.Empty;
     using (var sw = new ISO8859StringWriter())
     {
         XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
         ns.Add("", ""); // to omit XML namespaces
         var xs = new XmlSerializer(typeof(T));
         xs.Serialize(sw, objeto, ns);
      }
      return xml;
  }

These objects graph are generating a XML with the following schema:

<Servidor>
  <VersaoLayout>versao01</VersaoLayout>
  <CodigoUJ>999</CodigoUJ>
  <ItensLayout>
    <ServidorLayout>
      <CPFServidor>4252813450</CPFServidor>
      <NomeServidor>Antonio de Sousa Muniz</NomeServidor>
    </ServidorLayout>
  </ItensLayout>
</Serv>

I dont want the tag <ItensLayout> tag in the XML. What i need to do to generate the XML in the desired schema?

Also, the root tag <Servidor> will need to change according to the file i am generating, imagine that i need to create another file where the root tag is Another, let say:

LayoutArquivoXML<AnotherLayout> layout = new Layout.LayoutArquivoXML<AnotherLayout>("versao01", "999", anotherItensLayout.ToList());
Ewerton
  • 4,046
  • 4
  • 30
  • 56
  • 1
    You are asking independent questions. Your first question, how to make the element name match the generic parameter type name, is very similar to [C#: Best way to have XML element name from generic type name](https://stackoverflow.com/q/38344516/3744182). For your second question, about how to modify the root name of a generic, you will need to subclass `LayoutArquivoXML` or use `XmlRootAttribute` as shown [here](http://stackoverflow.com/a/23897411/3744182). – dbc Apr 26 '17 at 21:35

1 Answers1

1

There are two separate issues.

I don't want the tag <ItensLayout> tag in the XML. What i need to do to generate the XML in the desired schema?

As explained in the documentation for Serializing an Array as a Sequence of Elements, to serialize an array (list, enumerable etc.) as flat sequence of XML elements, you need to apply XmlElement attribute to the property/field.

Also, the root tag <Servidor> will need to change according to the file i am generating

This is controlled by the XmlRoot attribute.

Now, all this applies if you are using concrete classes. In order to apply them dynamically to your generic class, you can use the XmlOverrides class and the XmlSerializer constructors accepting such parameter.

To do that, first add an optional parameter to your original method:

public string Serializador<T>(T objeto, XmlAttributeOverrides overrides = null)
{
    string xml = string.Empty;
    using (var sw = new ISO8859StringWriter())
    {
        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
        ns.Add("", ""); // to omit XML namespaces
        var xs = new XmlSerializer(typeof(T), overrides);
        xs.Serialize(sw, objeto, ns);
        xml = sw.ToString();
    }
    return xml;
}

Then create a specific method like this:

public string Serializador<T>(LayoutArquivoXML<T> objeto, string rootElementName, string contentElementName)
     where T : ItemLayout
{
    var xmlAttrOverrides = new XmlAttributeOverrides();
    // Root element name override
    var xmlRootAttrs = new XmlAttributes();
    xmlRootAttrs.XmlRoot = new XmlRootAttribute(rootElementName);
    xmlAttrOverrides.Add(typeof(LayoutArquivoXML<T>), xmlRootAttrs);
    // Content element name override
    var xmlContentAttrs = new XmlAttributes();
    xmlContentAttrs.XmlElements.Add(new XmlElementAttribute(contentElementName));
    xmlAttrOverrides.Add(typeof(LayoutArquivoXML<T>), "ItensLayout", xmlContentAttrs);
    // Call the original method passing the overrides
    return Serializador(objeto, xmlAttrOverrides);
}

Now you can achieve your goal by using something like this:

var source = new LayoutArquivoXML<ServidorLayout>
{
    VersaoLayout = "0001",
    CodigoUJ = "001001",
    ItensLayout = new List<ServidorLayout>
    {
        new ServidorLayout
        {
            CPFServidor = "13579024681",
            Nome = "Fulano Pereira Tal",
        },
    }
};

var xml = Serializador(source, "Servidor", "ItemServidor");

which produces:

<Servidor>
  <VersaoLayout>0001</VersaoLayout>
  <CodigoUJ>001001</CodigoUJ>
  <ItemServidor>
    <CPFServidor>13579024681</CPFServidor>
    <NomeServidor>Fulano Pereira Tal</NomeServidor>
  </ItemServidor>
</Servidor>
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343