如何使用XmlSerializer序列化具有DefaultValueAttribute属性的属性?

4

我正在使用XmlSerializer将C#对象序列化为XML。我在一些需要序列化的类的属性上使用了DefaultValueAttribute,但当我尝试对它们进行序列化时,如果值等于默认值,则XmlSerializer似乎不会在xml中包含该值。

看下面这个例子:

using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace Test
{
    public class Person
    {
        [System.Xml.Serialization.XmlAttribute()]
        [System.ComponentModel.DefaultValue("John")]
        public string Name { get; set; }
    }

    public static class Test
    {
        public static void Main()
        {
            var serializer = new XmlSerializer(typeof(Person));
            var person = new Person { Name = "John" };

            using (var sw = new StringWriter())
            {
                using (var writer = XmlWriter.Create(sw))
                {
                    serializer.Serialize(writer, person);
                    var xml = sw.ToString();
                }
            }
        }
    }
}

它将产生以下XML(请注意,名称属性不可用):
<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

我无法修改类的源代码,因此无法删除 DefaultValueAttribute。是否有一种方法可以使 XmlSerializer 在不更改源代码的情况下序列化这些属性?

2个回答

5
当您创建 XmlSerializer 时,可以通过将 XmlAttributeOverrides 实例传递给它来实现此操作。您可以使用它将默认值更改为其他值,或将其设置为 null 以有效地删除它。
XmlAttributeOverrides attributeOverrides = new XmlAttributeOverrides();

var attributes = new XmlAttributes()
{
    XmlDefaultValue = null,
    XmlAttribute = new XmlAttributeAttribute()
};

attributeOverrides.Add(typeof(Person), "Name", attributes);

var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
var person = new Person { Name = "John" };

using (var sw = new StringWriter())
{
    using (var writer = XmlWriter.Create(sw))
    {
        serializer.Serialize(writer, person);
        var xml = sw.ToString();
    }
}

更新:以上意味着您必须在每个要更改的属性上再次提供其他无关属性。如果您有很多属性并且只想删除所有属性的默认值,这可能有点繁琐。可以使用下面的类来保留其他自定义属性,同时仅删除一种类型的属性。如果需要,可以进一步扩展以仅对某些属性执行此操作。
public class XmlAttributeOverrideGenerator<T>
{
    private static XmlAttributeOverrides _overrides;
    private static Type[] _ignoreAttributes = new Type[] { typeof(DefaultValueAttribute) };

    static XmlAttributeOverrideGenerator()
    {
        _overrides = Generate();
    }

    public static XmlAttributeOverrides Get()
    {
        return _overrides;
    }

    private static XmlAttributeOverrides Generate()
    {
        var xmlAttributeOverrides = new XmlAttributeOverrides();

        Type targetType = typeof(T);
        foreach (var property in targetType.GetProperties())
        {
            XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
            xmlAttributeOverrides.Add(targetType, property.Name, propertyAttributes);
        }

        return xmlAttributeOverrides;
    }

    public class CustomAttribProvider : ICustomAttributeProvider
    {
        private PropertyInfo _prop = null;
        private Type[] _ignoreTypes = null;            

        public CustomAttribProvider(PropertyInfo property, params Type[] ignoreTypes)
        {
            _ignoreTypes = ignoreTypes;
            _prop = property;
        }

        public object[] GetCustomAttributes(bool inherit)
        {
            var attribs = _prop.GetCustomAttributes(inherit);
            if (_ignoreTypes == null) return attribs;
            return attribs.Where(attrib => IsAllowedType(attrib)).ToArray();
        }

        private bool IsAllowedType(object attribute)
        {
            if (_ignoreTypes == null) return true;
            foreach (Type type in _ignoreTypes)
                if (attribute.GetType() == type)
                    return false;

            return true;
        }

        public object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            throw new NotImplementedException();
        }

        public bool IsDefined(Type attributeType, bool inherit)
        {
            throw new NotImplementedException();
        }
    }
}

使用方法:

XmlAttributeOverrides attributeOverrides = XmlAttributeOverrideGenerator<Person>.Get();            
var serializer = new XmlSerializer(typeof(Person), attributeOverrides);

谢谢回复! 如果我像这样覆盖它 attributeOverrides.Add(typeof(Person), "Name", new XmlAttributes() { XmlDefaultValue = null }); 那么所有的XmlAttributes都将被覆盖,在我的例子中 [System.Xml.Serialization.XmlAttribute()] 将不存在。是否有一种方法仅覆盖 DefaultValue 并保留所有其他属性不变? - Mykhailo Seniutovych
1
据我所知,目前还没有这样的功能,但是您可以手动设置每个属性的其他属性以恢复它们。我添加了一些代码,可以自动创建“XmlAttributeOverrides”来复制现有内容并进行自定义。在我的示例中,假设您想要删除给定类中所有属性的“DefaultValueAttribute”。 - steve16351

1

我没有足够的声望来评论steve16351的答案。但我觉得我对他的代码进行了一些改进。

我的用例涉及通过Microsoft提供的XSD.exe生成的XML模式文件,然后从模式文件生成了一个类。因此,该类具有来自模式的所有DefaultValue标记。还创建了许多嵌套类型。因此,为了使我的代码简洁,我修改了“Generate”方法以获取顶级类和其下面所有类的覆盖。然后将所有内容返回到单个XmlAttributesOverrides对象中。

static XmlAttributeOverrideGenerator()
{
    _overrides = Generate(typeof(T), new XmlAttributeOverrides());
}

private static XmlAttributeOverrides Generate(Type targetType, XmlAttributeOverrides Overrides)
{
    foreach (var property in targetType.GetProperties())
    {
        if (property.CustomAttributes.Any( CA => CA.AttributeType == typeof(DefaultValueAttribute)))
        {
            XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
            Overrides.Add(targetType, property.Name, propertyAttributes);
        }
        else
        {
            Type propType = property.PropertyType;
            if (propType.IsClass && !propType.IsValueType && !propType.IsEnum && !propType.IsPrimitive && !propType.IsSealed)  // Check if this is a custom class
            {
                //If this is a nested class or other class type, generate its overrides as well
                Generate(propType, Overrides);
            }
        }
    }

    return Overrides;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接