This is something that bit me recently:
The current version of
BizUnit (v.2.2.1003.0) uses the .NET 1.0
XmlValidatingReader to do schema validation in both the XmlValidationStep and XmlValidationStepEx steps.
.NET 2.0 deprecated the XmlValidatingReader class, proposing that you use the
XmlReader class and the new
XmlReaderSettings class instead.
Microsoft also introduced a slew of bug fixes to the validation logic – including the ability to correctly process Xsd import and include directives.
I was trying to test a fairly complex schema recently with BizUnit – a schema which has a lot of includes.
And BizUnit would consistently throw an exception.
Looking through the BizUnit source code, I noticed that the XmlValidatingReader was being used:
XmlDocument doc = new XmlDocument();
XmlTextReader trData = new XmlTextReader( data );
XmlValidatingReader vr = new XmlValidatingReader(trData);
// If schema was specified use it to validate against...
if ( null != xmlSchemaPath )
{
MemoryStream xsdSchema = StreamHelper.LoadFileToStream(xmlSchemaPath);
XmlTextReader trSchema = new XmlTextReader( xsdSchema );
XmlSchemaCollection xsc = new XmlSchemaCollection();
if ( null != ns )
{
xsc.Add( ns, trSchema );
vr.Schemas.Add(xsc);
}
doc.Load(vr);
}I've raised an issue on the BizUnit CodePlex site for this, but in the meantime I updated the
XmlValidationStep.cs source file to this:
XmlDocument doc = new XmlDocument();
XmlTextReader trData = new XmlTextReader(data);
// If schema was specified use it to vaidate against...
if (null != xmlSchemaPath)
{
FileInfo fi = new FileInfo(xmlSchemaPath);
// Store the current diretcory whilst we temporarily change it
string currentDirectory = Environment.CurrentDirectory;
// Change the current directory to the schema location so that
// any relative imports/includes in the schema will work correctly
Environment.CurrentDirectory = fi.DirectoryName;
MemoryStream xsdSchema = StreamHelper.LoadFileToStream(xmlSchemaPath);
XmlTextReader trSchema = new XmlTextReader(xsdSchema);
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
if (null != ns)
{
settings.Schemas.Add(ns, trSchema);
}
else
{
settings.Schemas.Add(null, trSchema);
}
XmlReader vr = XmlReader.Create(trData, settings);
while (vr.Read()) { }
// Reset the current directory
Environment.CurrentDirectory = currentDirectory;
}
Note: Interestingly, the BizTalk XmlValidator pipeline component also uses the .NET 1.0 classes...
I also made two other changes:
- There's a bug (well, I see it as a bug!) in the current XmlValidationStep.
When XmlDocument.SelectSingleNode is called, and an XmlNode returned, the code doesn't check if the returned node is null before trying to access the InnerText property on the node:
XmlNode checkNode = doc.SelectSingleNode( xpathExp );
if ( 0 != nodeValue.CompareTo( checkNode.InnerText ) )
If the xPath statement you're using returns no nodes (usually because you typed it in wrong!), then this will throw an exception.
Your test will fail (as expected) but instead of a useful error, you'll get an "object reference not set to an instance of an object" error...
- It's always seemed a bit strange to me that there is both an XmlValidationStep and an XmlValidationStepEx – one uses XmlDocument.SelectSingleNode, the other uses an XPathNavigator. I'm sure there's a good reason for it.
Statements such as count(.) will throw an XPathException() with SelectSingleNode, but will work correctly with an XPathNavigator.
So I combined both approaches in my XmlValidationStep: SelectSingleNode is tried first. If this throws an exception, then an XPathNavigator is used:
XmlNode checkNode = null;
try
{
checkNode = doc.SelectSingleNode(xpathExp);
}
catch { }
if (checkNode != null)
{
if (0 != nodeValue.CompareTo(checkNode.InnerText))
{
throw new ApplicationException(string.Format("XmlValidationStepEnhanced failed, compare {0} != {1}, xpath query used: {2}", nodeValue, checkNode.InnerText, xpathExp));
}
}
else
{
XPathNavigator xpn = doc.CreateNavigator();
object result = xpn.Evaluate(xpathExp);
string checkValue = null;
if (result.GetType().Name == "XPathSelectionIterator")
{
XPathNodeIterator xpi = result as XPathNodeIterator;
xpi.MoveNext();
if (null != xpi)
{
checkValue = xpi.Current.ToString();
}
}
else
{
checkValue = result.ToString();
}
if (0 != nodeValue.CompareTo(checkValue))
Works for me!
Not sure if I should do this (!) but here's a version of my updated
XmlValidationStep.cs file.
If you wish you can grab the source file and compile your own version of the core BizUnit assembly should you need this functionality as well.
XmlValidationStep.cs