Сериализация, коллекция и корневой элемент C# Xml

мое приложение сериализует объекты в потоки. Вот пример того, что мне нужно:

<links>
  <link href="/users" rel="users" />
  <link href="/features" rel="features" />
</links>

в этом случае объект представляет собой совокупность объекта ссылки.

-----------первая версия

сначала я использовал DataContractSerializer, однако вы не можете сериализовать члены как атрибуты (источник)

вот объект:

[DataContract(Name="link")]
public class LinkV1
{
    [DataMember(Name="href")]
    public string Url { get; set; }

    [DataMember(Name="rel")]
    public string Relationship { get; set; }
}

и вот результат :

<ArrayOflink xmlns:i="...." xmlns="...">
  <link>
    <href>/users</href>
    <rel>users</rel>
  </link>
  <link>
    <href>/features</href>
    <rel>features</rel>
  </link>
</ArrayOflink>

----------- Вторая версия

хорошо, не тихо, что я хочу, поэтому я попробовал классический XmlSerializer, но... о нееет, вы не можете указать имя корневого элемента и элементов коллекции, если корневой элемент коллекции...

вот код :

[XmlRoot("link")]
public class LinkV2
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

вот результат :

<ArrayOfLinkV2>
  <LinkV2 href="/users" rel="users" />
  <LinkV2 href="/features" rel="features" />
  <LinkV2 href="/features/user/{keyUser}" rel="featuresByUser" />
</ArrayOfLinkV2>

----------- третья версия

используя XmlSerializer + корневого элемента :

[XmlRoot("trick")]
public class TotallyUselessClass
{
    [XmlArray("links"), XmlArrayItem("link")]
    public List<LinkV2> Links { get; set; }
}

и его результат :

 <trick>
  <links>
    <link href="/users" rel="users" />
    <link href="/features" rel="features" />
    <link href="/features/user/{keyUser}" rel="featuresByUser" />
  </links>
</trick>

хорошо, но я не хочу этот корневой узел !! Я хочу, чтобы моя коллекция была корневым узлом.

вот противопоказания:

  • код сериализации является общим, он работает с чем-либо сериализуемым
  • обратная операция (десериализация) тоже должна работать
  • я не хочу, чтобы регулярное выражение результат (я сериализовать непосредственно в выходной поток)

Каковы мои решения сейчас :

  1. кодирование моего собственного XmlSerializer
  2. Trick XmlSerializer, когда он работает с коллекцией (я попытался, найдя XmlRootElement и plurialize его, чтобы создать свой собственный XmlRootAttribute, но это вызывает проблему при десериализации + имя элементов по-прежнему сохраняет имя класса)

есть идеи ?

что действительно беспокоит меня в этом вопросе, так это то, что я хочу, кажется, действительно очень, очень просто...

3 ответов


Ok, вот мое окончательное решение (надеюсь, что это поможет кому-то), которое может сериализовать простой массив, List, HashSet,...

чтобы достичь этого, нам нужно сказать сериализатору, какой корневой узел использовать, и это довольно сложно...

1) Используйте 'XmlType' на сериализуемом объекте

[XmlType("link")]
public class LinkFinalVersion
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

2) код метода "smart-root-detector-for-collection", который вернет XmlRootAttribute

private XmlRootAttribute XmlRootForCollection(Type type)
{
    XmlRootAttribute result = null;

    Type typeInner = null;
    if(type.IsGenericType)
    {
        var typeGeneric = type.GetGenericArguments()[0];
        var typeCollection = typeof (ICollection<>).MakeGenericType(typeGeneric);
        if(typeCollection.IsAssignableFrom(type))
            typeInner = typeGeneric;
    }
    else if(typeof (ICollection).IsAssignableFrom(type)
        && type.HasElementType)
    {
        typeInner = type.GetElementType();
    }

    // yeepeeh ! if we are working with a collection
    if(typeInner != null)
    {
        var attributes = typeInner.GetCustomAttributes(typeof (XmlTypeAttribute), true);
        if((attributes != null)
            && (attributes.Length > 0))
        {
            var typeName = (attributes[0] as XmlTypeAttribute).TypeName + 's';
            result = new XmlRootAttribute(typeName);
        }
    }
    return result;
}

3) Нажмите, что XmlRootAttribute в сериализатор

// hack : get the XmlRootAttribute if the item is a collection
var root = XmlRootForCollection(type);
// create the serializer
var serializer = new XmlSerializer(type, root);

Я же говорил, что это сложно;)


чтобы улучшить это, вы можете :

A) создайте XmlTypeInCollectionAttribute, чтобы указать пользовательское корневое имя (если базовая плюрализация не соответствует вашим потребностям)

[XmlType("link")]
[XmlTypeInCollection("links")]
public class LinkFinalVersion
{
}

B) по возможности кэшируйте XmlSerializer (например, в статическом словаре).

в моем тестировании создание экземпляра XmlSerializer без XmlRootAttributes занимает 3 мс. Если указать XmlRootAttribute, это занимает около 80 мс (просто чтобы иметь пользовательское имя корневого узла !)


XmlSerializer должен иметь возможность делать то, что вам нужно, но он сильно зависит от начальной структуры и настройки. Я использую его в своем собственном коде для создания удивительно похожих вещей.

public class Links<Link> : BaseArrayClass<Link> //use whatever base collection extension you actually need here
{
    //...stuff...//
}

public class Link
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

теперь, сериализация Links класс должен создать именно то, что вы ищете.

проблема с XmlSerializer заключается в том, что когда вы даете ему дженерики, он отвечает дженериками. List Implemets Array где-то там, и сериализованный результат почти всегда будет ArrayOf<X>. Чтобы обойти это, вы можете назвать свойство или корень класса. Закрывается то, что вам нужно, вероятно, вторая версия из ваших примеров. Im предполагая, что вы попытались прямую сериализацию ссылок списка объектов. Это не сработает, потому что вы не указали корневой узел. Теперь аналогичный подход можно найти здесь. В этом они указывают XmlRootAttribute при объявлении сериализатора. Ваш будет выглядеть так:

XmlSerializer xs = new XmlSerializer(typeof(List<Link>), new XmlRootAttribute("Links"));

здесь вы идете...

 class Program
{
    static void Main(string[] args)
    {

        Links ls = new Links();
        ls.Link.Add(new Link() { Name = "Mike", Url = "www.xml.com" });
        ls.Link.Add(new Link() { Name = "Jim", Url = "www.xml.com" });
        ls.Link.Add(new Link() { Name = "Peter", Url = "www.xml.com" });

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(Links));

        StringWriter stringWriter = new StringWriter();

        xmlSerializer.Serialize(stringWriter, ls);

        string serializedXML = stringWriter.ToString();

        Console.WriteLine(serializedXML);

        Console.ReadLine();
    }
}

[XmlRoot("Links")]
public class Links
{
    public Links()
    {
        Link = new List<Link>();
    }

    [XmlElement]
    public List<Link> Link { get; set; }
}

[XmlType("Link")]
public class Link
{
    [XmlAttribute("Name")]
    public string Name { get; set; }


    [XmlAttribute("Href")]
    public string Url { get; set; }

}