Monday 24 March 2014

XML Validation against a schema in C#

There are two ways to validate an incoming an XML message against a schema:

1. Preload the schemas into the validator
2. Resolve and load the resources during processing

Doing both is incompatible and you will end up with message "The global element 'TopElementName' has already been declared".

Method 1

The schemas are loaded into the XmlReaderSettings.

public void Validate(string xml)
        {
            // Thread.CurrentThread.ManagedThreadId.Dump();
            ValidationEvents = new List();

            // Get the embedded resource from the DLL
            var resourceAssembly = this.GetType().Assembly;
            var schemas = LoadSchemasFromEmbeddedResource(resourceAssembly, new Dictionary() 
         { 
          { "http://www.w3.org/2000/09/xmldsig#", "NewBusiness.Domain.Schema_Resources.xmldsig-core-schema.xsd" },   // XmlSchemaValidationException is thrown without inclusion
                { "http://www.origostandards.com/schema/soap/v1", "NewBusiness.Domain.Schema_Resources.SOAPMessageControl.xsd" },
                { "http://www.origostandards.com/schema/ProcessSIPPIDPRApplication/v1.0/Common/CreateRequest", "NewBusiness.Domain.Schema_Resources.ProcessSIPPIDPRApplicationCommonCreateRequest.xsd" },
                { "http://www.origostandards.com/schema/ProcessSIPPIDPRApplication/v1.0/CreateRequest", "NewBusiness.Domain.Schema_Resources.ProcessSIPPIDPRApplicationCreateRequest.xsd" }
         });

            // Set the validation settings.
            var settings = new XmlReaderSettings();
            settings.Schemas = schemas;
            settings.ValidationType = ValidationType.Schema;
            settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
            // settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;      // Allows the processor to load the schema from the user-defined location
            // settings.XmlResolver = new XmlResourceResolver();                                   // If ProcessSchemaLocation is on then the resolver searches for resources in the assembly
            settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
            settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
            settings.DtdProcessing = DtdProcessing.Ignore;

            // Create the XmlReader object.
            try
            {
                using (var textReader = new StringReader(xml))
                {
                    using (var reader = XmlReader.Create(textReader, settings))
                    {
                        while (reader.Read()) ;
                    }
                }
            }
            catch (XmlSchemaValidationException schemaException)
            {
                ValidationEvents.Add(new ValidationEvent() { Severity = XmlSeverityType.Error, Message = "Schema load error: " + schemaException.Message });
            }
        }

private XmlSchemaSet LoadSchemasFromEmbeddedResource(Assembly assembly, Dictionary namespacePaths)
        {
            var schemaSet = new XmlSchemaSet();
            var readerSettings = new XmlReaderSettings 
            { 
                XmlResolver = new XmlResourceResolver(), 
                ValidationType = ValidationType.None,
                DtdProcessing = DtdProcessing.Ignore,
            };
            readerSettings.ValidationEventHandler += new ValidationEventHandler(SchemaLoadCallBack);

            foreach (var entry in namespacePaths)
            {
                using (var stream = assembly.GetManifestResourceStream(entry.Value))
                {
                    // stream.Seek(0, SeekOrigin.Begin);

                    var reader = XmlReader.Create(stream, readerSettings);
                    schemaSet.Add(entry.Key, reader);
                }
            }

            return schemaSet;
        }

Method 2 involves the following two lines of code:

settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;      // Allows the processor to load the schema from the user-defined location
settings.XmlResolver = new XmlResourceResolver();                                   // If ProcessSchemaLocation is on then the resolver searches for resources in the assembly

The ProcessSchemaLocation means that the reader will process a schema location that is specified in the XML (shown in bold):

<Message xsi:schemaLocation="http://www.origostandards.com/schema/ProcessSIPPIDPRApplication/v1.0/CreateRequest ProcessSIPPIDPRApplicationCreateRequest.xsd">
</Message>

This allows the schema location to be specified in the incoming XML and the reader will try and load it.
The code below shows a custom XML resolver that will attempt to load the schema from an embedded resource included within the current assembly.

public class XmlResourceResolver : XmlResolver
    {
        public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
        {
            var settings = new XmlReaderSettings();
            settings.ValidationType = ValidationType.None;

            if (absoluteUri.Segments.Length > 0)
            {
                var filename = absoluteUri.Segments[absoluteUri.Segments.Length - 1];

                var assembly = this.GetType().Assembly;
                var stream = assembly.GetManifestResourceStream("NewBusiness.Domain.Schema_Resources." + filename);
                return stream;
            }

            return null;
        }

        private void ValidationEventHandler(object sender, ValidationEventArgs e)
        {
        }


No comments:

Post a Comment