Patrick Q
Patrick Q

Reputation: 133

C# XML not correctly validating against Schema in XmlReaderSettings

I searched and did not find any questions addressing this problem.

I am attempting to validate various XML against a schema and it seems to be validating ALL well-formed XML, instead of just XML that conforms to the schema. I have posted the code I am using, the Schema, a sample valid XML and a sample invalid XML.

I have been struggling with this for awhile. I am in the dark on most of this. I've had to learn how to write an XSD, write the XSD, then learn how to parse XML in C#. None of which I have ever done before. I have used many tutorials and the microsoft website to come up with the following. I think this should work, but it doesn't.

What am I doing wrong?

private bool ValidateXmlAgainstSchema(string sourceXml, string schemaUri)
{
    bool validated = false;
    try
    {
        //  REF:
        //  This should create a SCHEMA-VALIDATING XMLREADER
        //  http://msdn.microsoft.com/en-us/library/w5aahf2a(v=vs.110).aspx

        XmlReaderSettings xmlSettings = new XmlReaderSettings();
        xmlSettings.Schemas.Add("MySchema.xsd", schemaUri);
        xmlSettings.ValidationType = ValidationType.Schema;
        xmlSettings.ValidationFlags = XmlSchemaValidationFlags.None;
        XmlReader xmlReader = XmlReader.Create(new StringReader(sourceXml), xmlSettings);

        //  parse the input (not sure this is needed)
        while (xmlReader.Read()) ;

        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(xmlReader);

        validated = true;
    }
    catch (XmlException e)
    {
        //  load or parse error in the XML
        validated = false;
    }
    catch (XmlSchemaValidationException e)
    {
        //  Validation failure in XML
        validated = false;
    }
    catch (Exception e)
    {
        validated = false;
    }
    return validated;
}

The XSD / Schema. The intent is to accept XML that contains either an Incident or a PersonOfInterest.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="MySchema.xsd"
    xmlns="MySchema.xsd"
    elementFormDefault="qualified"
>
  <xs:element name="Incident" type="IncidentType"/>
  <xs:element name="PersonOfInterest" type="PersonOfInterestType"/>

  <xs:complexType name="IncidentType">
    <xs:sequence>
      <xs:element name="Description" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
      <xs:element name="PersonOfInterest" type="PersonOfInterestType" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="PersonOfInterestType">
    <xs:sequence>
      <xs:element name="Name" type="xs:string" minOccurs="1" maxOccurs="1"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>

Here is a sample of valid XML

<?xml version="1.0" encoding="utf-8" ?>

  <Incident
      xmlns="MySchema.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.w3schools.com MySchema.xsd"
  >
    <Description>something happened</Description>
    <PersonOfInterest>
        <Name>Joe</Name>
    </PersonOfInterest>
    <PersonOfInterest>
        <Name>Sue</Name>
    </PersonOfInterest>
  </Incident>

This is a sample of well-formed invalid XML which should throw an exception (I thought), but when I try it, the code returns true, indicating it is valid against the schema.

<ghost>Boo</ghost>

Upvotes: 5

Views: 6788

Answers (2)

DavidC
DavidC

Reputation: 704

Just to add that you can enable warnings:

xmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;

so you can get a message for an entry like <ghost>Boo</ghost>

Upvotes: 0

tom redfern
tom redfern

Reputation: 31760

The reason your <ghost>Boo</ghost> validates is that the parser cannot find any schema matching the xml. If there is no schema then the parser assumed validity, providing the xml is well-formed. It's counter-intuitive I know, and will probably differ based on parser implementation.

This notwithstanding, there are several problems with your code:

Two Root Elements

This is a big no-no in xsd - you can only have a single root element. Some parsers will actually throw an exception, others tolerate it but will only use the first root element (in your case Incident) for any subsequent validation.

Use of schemaLocation attribute

This should take the value (namespace) (URI) where the namespace is the targetNamespace of the schema and the URI is the location of the schema. In your case you appear to be using the schema file name as your target namespace. Additionally, looking at your code, you are loading the schema into your xml reader so you don't actually need the schemaLocation attribute at all. This is an optional attribute and some parsers completely ignore it.

I would suggest the following changes:

<xs:schema
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://MyMoreMeaningfulNamespace"
    xmlns="http://MyMoreMeaningfulNamespace"
    elementFormDefault="qualified"
>
  <xs:element name="Root">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="Incident" type="IncidentType"/>
        <xs:element maxOccurs="unbounded" name="PersonOfInterest" type="PersonOfInterestType"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:complexType name="IncidentType">
    <xs:sequence>
      <xs:element name="Description" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
      <xs:element name="PersonOfInterest" type="PersonOfInterestType" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="PersonOfInterestType">
    <xs:sequence>
      <xs:element name="Name" type="xs:string" minOccurs="1" maxOccurs="1"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>

Which validates this instance

<Root xmlns="http://MyMoreMeaningfulNamespace">
  <Incident>
    <Description>something happened</Description>
    <PersonOfInterest>
      <Name>Joe</Name>
    </PersonOfInterest>
    <PersonOfInterest>
      <Name>Sue</Name>
    </PersonOfInterest>
  </Incident>

  <Incident>
    ...
  </Incident>

  <PersonOfInterest>
    <Name>Manny</Name>
  </PersonOfInterest>

  <PersonOfInterest>
    ...
  </PersonOfInterest> 

</Root>

Upvotes: 5

Related Questions