Custom XML Formatter добавляет только необходимые пространства имен - PullRequest
0 голосов
/ 19 февраля 2020

Мне нужно добавить префиксы пространства имен в XML, сгенерированные некоторыми из моих конечных точек API, из-за того, что клиентское приложение использует xpaths с жестко закодированными префиксами (не начинайте).

Я успешно создал пользовательский XmlSerializerOutputFormatter, который переопределяет метод Serialize следующим образом:

    public class CustomXmlOutputFormatter : XmlSerializerOutputFormatter
    {
        protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
        {
            var namespaces = new XmlQualifiedName[]
            {
                new XmlQualifiedName("xsi", "http://www.w3.org/2001/XMLSchema-instance"),
                new XmlQualifiedName("t1", "my.namespace.one"),
                new XmlQualifiedName("t2", "my.namespace.two"),
                new XmlQualifiedName("t3", "my.namespace.three"),

            };

            var nsManager = new XmlSerializerNamespaces(namespaces);                        
            xmlSerializer.Serialize(xmlWriter, value, nsManager);
        }
    }

И это работает, поскольку XML создается с использованием правильного префикса пространства имен.

Однако я ' Теперь я получаю все мои префиксы пространства имен в каждом сгенерированном XML, даже если они не используются. Например, xml ниже использует только элементы из пространства имен "t1", но, как я объявил все три в менеджере пространства имен, я получаю все три в xml:

<t1:MyRootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xmlns:t1="my.namespace.one" 
            xmlns:t2="my.namespace.two" 
            xmlns:t3="my.namespace.three">
    <t1:Hello>World</t1:Hello>
</t1:MyRootNode>

Это значительно увеличивает размер XML, так как у нас есть более 200 различных пространств имен, которые в общей сложности должны использоваться COULD - но каждая конечная точка фактически использует только два или три различных пространства имен макс.

То, что я ищу, - это способ включить только пространства имен, которые фактически используются сериализуемым объектом (или любым из его дочерних объектов).

Поскольку все мои объекты аннотируются с помощью соответствующего XML атрибуты сериализации (такие как [XmlElement]) и они используют параметр пространства имен, где это необходимо, я думаю, я мог бы написать какое-то ужасное отражение в go вниз по кроличьей норе и найти любые пространства имен, используемые таким образом.

Однако я надеюсь, что есть более элегантное (и, вероятно, более очевидное) решение, которое я не вижу:)

Есть идеи?

1 Ответ

0 голосов
/ 21 февраля 2020

Вот как я реализовал это с помощью отражения:

    protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
    {
        //this is a List<XmlQualifiedName>() that contains all the namespaces and prefixes we could be using - its dynamically built during the constructor from a json file for ease of use.
        var allNamespaces = AllPossibleNamespaces.ToList(); //create a temp copy we can modify

        //remove surplus namespaces
        var usedNamespaces = GetUsedNamespaces(value.GetType());
        allNamespaces.RemoveAll(n => !usedNamespaces.Contains(n.Namespace));

        //now add in the default ones we always want
        allNamespaces.AddRange(requiredNamespaces);

        //finally, serialize the object using the namespaces and prefixes
        var nsManager = new XmlSerializerNamespaces(allNamespaces.ToArray());
        xmlSerializer.Serialize(xmlWriter, value,nsManager);            
    }

    private List<string> GetUsedNamespaces(Type baseType)
    {
        var usedNamespaces = new List<string>();

        var attribs = baseType.GetXmlAttributes();
        baseType.GetProperties().ToList().ForEach(p => attribs.AddRange(p.GetXmlAttributes()));

        foreach (var a in attribs)
        {
            var nsProp = a.GetType().GetProperty("Namespace",typeof(string));
            if (nsProp!=null)
            {
                var ns = (string)nsProp.GetValue(a);
                if (!string.IsNullOrWhiteSpace(ns) && !usedNamespaces.Contains(ns))
                    usedNamespaces.Add(ns);
            }
        }

        foreach(var child in baseType.GetProperties())
        {
            var propType = child.PropertyType;
            var type = propType;
            if (propType.IsClass)
            {
                //dont check inside strings :)
                if (propType != typeof(string))
                {
                    foreach (var iface in propType.GetInterfaces())
                    {
                        if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IList<>))
                        {
                            type = propType.GetGenericArguments()[0];
                        }
                    }
                    GetUsedNamespaces(type).ForEach(n => { 
                        if (!usedNamespaces.Contains(n)) 
                            usedNamespaces.Add(n);
                    });                        
                }
            }                
        }

        return usedNamespaces;
    }   

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

...