4

I need to mark some fields as required to be writen in XML file, but no success. I have a configuration class with ~30 properties, this is why i cannot envelope all properties like this

    public string SomeProp
    {
        get { return _someProp; }
        set
        {
            if (_someProp == null)
                throw  new ArgumentNullException("value");
            _someProp = value;
        }
    }

and null is valid value from code, but not from XML. I'm trying to use XmlAttribute, but it doesn't affect like I expected (IOException when deserialising).

For example this code:

    [XmlElement(IsNullable = true)]
    public String Name { get; set; }

    [XmlElement(IsNullable = true)]
    public String Description { get; set; }

is pretty valid when Name and Description tags are missing.


clarification:

Class:

public class MyClass
{
    [XmlElement(IsNullable = true)]
    public String Name { get; set; }

    [XmlElement(IsNullable = true)]
    public String Description { get; set; } 
}

XML:

<?xml version="1.0"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>hello</Name>
</MyClass>

code:

MyClass deserialize = (MyClass)new XmlSerializer(my.GetType()).Deserialize(new FileStream("result.xml", FileMode.Open));
Console.WriteLine(deserialize.Name);
Console.WriteLine(deserialize.Description);

result: deserialization is successful, Description is null

expected: exception, Description tag not found.

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • Can you clarify when you want to throw when a null value is encountered. During Serialize or Deserialize? – rene Jun 16 '14 at 09:09

2 Answers2

3

I believe you could follow two approaches. One would be the use of a validating XmlReader that uses a schema, the other one is a reflection check AFTER the deserialization. The last option is the solution I provide here:

This helper takes a stream and deserialize that given the generic type to an instance of that type. Once that object exists its properties can be iterated and checked if the property is decorated with the XmlElementAttribute. If that attribute is found its IsNullable property is checked and if false AND the property on the object instance is false we throw an exception.

public T ValidatingDeserializer<T>(Stream s)
{
    T deserialize = (T)new XmlSerializer( typeof(T)).Deserialize(s);

    foreach(var pi in typeof(T).GetProperties())
    {
        var xmlnull = pi.GetCustomAttribute(typeof(XmlElementAttribute));
        if (xmlnull!=null)
        {   var xmlnullInst = (XmlElementAttribute) xmlnull;
            if (!xmlnullInst.IsNullable && pi.GetValue(deserialize)==null)
            {
                throw new Exception(String.Format("{0} is null", pi.Name));
            }
        }
    }
    return deserialize;
}

Usage

MyClass deserialize = ValidatingDeserializer<MyClass>(
     new FileStream("result.xml", FileMode.Open));

Console.WriteLine(deserialize.Name);
Console.WriteLine(deserialize.Description);

Alternative with a validating based on a schema

By using a schema you could verify if the supplied XML document is valid against the given schema.

ReaderFactory

This takes an xml stream and a schema stream, hooksup an event where errors are raised and returns an XmlReader that will be the input for the Deserialize method.

public XmlReader ReaderFactory(Stream xml, Stream schema)
{
    XmlSchemaSet sc = new XmlSchemaSet();

    // Add the schema to the collection.
    sc.Add(null, XmlReader.Create(schema));

    // Set the validation settings.
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.ValidationType = ValidationType.Schema;
    settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;

    settings.Schemas = sc;
    settings.ValidationEventHandler += new ValidationEventHandler (ValidationCallBack);

    return XmlReader.Create(xml, settings);
}

The ValidateEvent target

 private static void ValidationCallBack(object sender, ValidationEventArgs e) {
    Console.WriteLine(
        "Validation Error: {0} near line: {1}, pos: {2}", 
        e.Message, 
        e.Exception.LineNumber, 
        e.Exception.LinePosition);
    throw new Exception("illegal xml content");
  }

Used sample schema for the xml shown in your question

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns=""
            elementFormDefault="qualified">

    <xsd:element name="MyClass" >
        <xsd:complexType>
            <xsd:sequence maxOccurs="1">
                <xsd:element name="Name" type="xsd:string"/>
                <xsd:element name="Description" type="xsd:string" minOccurs="1">
                </xsd:element>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>
rene
  • 41,474
  • 78
  • 114
  • 152
  • It's nice, but it should works recursively. So all properties of all fields and properties etc should be checked. This is why I hope on standard realisation of serialization. Because I can check everything manualy, but it will cost me dearly – Alex Zhukovskiy Jun 16 '14 at 11:11
  • And the properties of those classes are decorated as well? – rene Jun 16 '14 at 11:17
  • Sure. I know that we can check all props, but so many reflection seems be too bad for me. – Alex Zhukovskiy Jun 16 '14 at 11:18
  • Why is that? Is this in a time/resource critical path? – rene Jun 16 '14 at 12:42
  • I just do not like Reflection. But your answer is the best at this moment. So I mark it, tnx. – Alex Zhukovskiy Jun 16 '14 at 12:51
  • I added an alternative using an xsd schema – rene Jun 17 '14 at 21:54
  • Hi - I used your ValidatingDeserializer function. It works fine if the main Class property is marked with isNullable=false. Unfortunately, if one of your properties is a complex type from another child class that has an isNullable=false on one of its properties, the error is never triggered. – David P Mar 15 '17 at 20:58
  • @DavidP yeah, that implementation needs to be called recursively for complex-types (but that also requires handling of possible circular references). – rene Mar 15 '17 at 22:27
  • Validation cannot be done using Attributes? That seems clunky. – jeromej May 30 '23 at 08:50
3

Do you have to use XmlSerializer? DataContractSerializer provides the following option:

[DataMember(IsRequired = true)]
Tim Abell
  • 11,186
  • 8
  • 79
  • 110
Thierry
  • 342
  • 2
  • 6
  • 1
    Unfortunately - you can't decorate properties as Attributes with Data Contract Serializer, and so I couldn't use this. Have to remain with XMLSerializer, but still have issue. – David P Mar 15 '17 at 21:03