Могу ли я иметь атрибут null и другой атрибут в том же теге в XML, создаваемом классом сгенерированного XSD С#?

У меня есть куча классов С#, которые автоматически генерируются из XSD. Затем я создаю файлы XML на основе этих классов С#. Пока ничего не существует.

Проблема:

Сгенерированные XML файлы проходят проверку, а для проверки требуется дополнительный атрибут для всех тегов XML с помощью xsi:nil="true". В основном теги должны выглядеть так: <testTag.01 xsi:nil="true" NV="123123" />, но я не могу добиться этого в С#. Мой код:

     if (myObject.TestTag.HasValue)
        {
            t.testTag01 = new testTag01();
            t.testTag01.Value = myObject.TestTag.Value;
        }
        //else
        //{
        //    t.testTag01 = new testTag01();
        //    t.testTag01.NV = "123123";//Not Recorded
        //}

Этот код генерирует <testTag.01>SomeValue</testTag.01> или <testTag.01 xsi:nil="true"/>.

Если я раскомментирую ELSE, результатом будет: <testTag.01>SomeValue</testTag.01> или <testTag.01 NV="123123" />.

Поэтому я не знаю, как добраться до формата, который требуется инструментом проверки. Любые идеи?

P.S.

Вот авто-сгенерированный класс С#:

///[System.CodeDom.Compiler.GeneratedCodeAttribute( "xsd", "4.0.30319.33440" )] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute( "код" )] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = верно, Пространство имен = "http://www.blabla.org" )]

открытый частичный класс testTag01 {

private string nvField;

private SomeEnum valueField;

/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string NV {
    get {
        return this.nvField;
    }
    set {
        this.nvField = value;
    }
}

/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public SomeEnum Value {
    get {
        return this.valueField;
    }
    set {
        this.valueField = value;
    }
} }

Я бы не хотел изменять эту часть, но я понимаю, что это невозможно без этого. Также я попытался установить SomeEnum как Nullable. public SomeEnum? Value, но бросает исключение:

Cannot serialize member 'Value' of type System.Nullable`1[]. XmlAttribute/XmlText cannot be used to encode complex types.

Ответ 1

XmlSerializer не поддерживает прямую привязку к элементам, которые одновременно имеют xsi:nil="true" вместе с другими значениями атрибутов; см. Xsi: nil Поддержка привязки атрибутов: атрибут nil и другие атрибуты.

Таким образом, вам нужно исправить атрибут вручную.

Если вы хотите создать элемент без содержимого и двух атрибутов, один из которых называется NV, а другой всегда xsi:nil="true", вы можете изменить свой класс testTag01, чтобы иметь свойство NV как а также синтетическое свойство, имеющее правильное пространство имен и имя:

public class testTag01 
{
    [XmlAttribute]
    public string NV { get; set; }

    [XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
    public string Nil { get { return "true"; } set { } }
}

Если вы иногда хотите иметь xsi:nil="true", но в других случаях хотите, чтобы элемент имел контент, соответствующий вашему SomeEnum, вам нужно сделать что-то более сложное, так как xsi:nil="true" должен быть подавлен, когда элемент имеет содержание:

public class testTag01
{
    [XmlAttribute]
    public string NV { get; set; }

    [XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
    public string Nil { get { return SomeEnum == null ? "true" : null; } set { } }

    public bool ShouldSerializeNil() { return SomeEnum == null; }

    [XmlIgnore]
    public SomeEnum? SomeEnum { get; set; }

    [XmlText]
    public string SomeEnumText
    {
        get
        {
            if (SomeEnum == null)
                return null;
            return SomeEnum.Value.ToString();
        }
        set
        {
            // See here if one needs to parse XmlEnumAttribute attributes
            // http://stackoverflow.com/questions/3047125/retrieve-enum-value-based-on-xmlenumattribute-name-value
            value = value.Trim();
            if (string.IsNullOrEmpty(value))
                SomeEnum = null;
            else
            {
                try
                {
                    SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, false);
                }
                catch (Exception)
                {
                    SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, true);
                }
            }
        }
    }
}

(Элемент, который одновременно имеет и xsi:nil="true" и контент, будет нарушением стандарта XML, надеюсь, у вас нет этого.)

Затем используйте его как:

public class TestClass
{
    [XmlElement("testTag.01")]
    public testTag01 TestTag { get; set; }

    public static void Test()
    {
        Test(new TestClass { TestTag = new testTag01 { NV = "123123" } });
        Test(new TestClass { TestTag = new testTag01 { NV = "123123", SomeEnum = SomeEnum.SomeValue } });
    }

    private static void Test(TestClass test)
    {
        var xml = test.GetXml();

        var test2 = xml.LoadFromXML<TestClass>();

        Console.WriteLine(test2.GetXml());
        Debug.WriteLine(test2.GetXml());

        if (test2.TestTag.NV != test.TestTag.NV)
        {
            throw new InvalidOperationException("test2.TestTag.NV != test.TestTag.NV");
        }
    }
}

Выход XML выглядит следующим образом:

<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <testTag.01 NV="123123" xsi:nil="true" />
</TestClass>

или

<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <testTag.01 NV="123123">SomeValue</testTag.01>
</TestClass>

Прототип fiddle с использованием этих методов расширения:

public static class XmlSerializationHelper
{
    public static T LoadFromXML<T>(this string xmlString, XmlSerializer serializer = null)
    {
        T returnValue = default(T);

        using (StringReader reader = new StringReader(xmlString))
        {
            object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
            if (result is T)
            {
                returnValue = (T)result;
            }
        }
        return returnValue;
    }

    public static string GetXml<T>(this T obj, XmlSerializerNamespaces ns = null, XmlWriterSettings settings = null, XmlSerializer serializer = null)
    {
        using (var textWriter = new StringWriter())
        {
            settings = settings ?? new XmlWriterSettings() { Indent = true, IndentChars = "  " }; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
                (serializer ?? new XmlSerializer(typeof(T))).Serialize(xmlWriter, obj, ns);
            return textWriter.ToString();
        }
    }
}

Ответ 2

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

Я разбираю сгенерированный XML, и если я ищу node с атрибутом xsi: nil, но без атрибута NV, я добавляю атрибут NV со значением по умолчанию. То же самое для узлов с атрибутом NV, но без xsi: nil.

Вот код:

        XmlDocument doc = new XmlDocument();// instantiate XmlDocument and load XML from file
        doc.Load("somepath.xml");

        //Get the nodes with NV attribute(using XPath) and add xsi:nill to that nodes
        XmlNodeList nodes = doc.SelectNodes("//*[@NV]");

        foreach (XmlNode node in nodes)
        {
            XmlAttribute nilAttr = doc.CreateAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance");
            nilAttr.Value = "true";
            node.Attributes.Append(nilAttr);
        }

        //Get the nodes with xsi:nill attribute(using XPath) and add NV with default value to that nodes
        XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
        nsManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
        XmlNodeList nilNodes = doc.SelectNodes("//*[@xsi:nil]", nsManager);

        foreach (XmlNode node in nilNodes)
        {
            XmlAttribute nvAttr = doc.CreateAttribute("NV");
            nvAttr.Value = "7701003";
            node.Attributes.Append(nvAttr);
        }

        doc.Save("somepath.xml");

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