Question

I need to validate xml in sql server against an xsd. The xsd is too complex to use with XML SCHEMA COLLECTION, so I am writing a SQL CLR function to do it. I have two issues with how I have "had" to write the code. Code is VB.NET targeting 2.0, btw, though I think I run into the same issue in C#. If not happy to switch.

[1] I cannot seem to attach settings to the reader created by SqlXml.CreateReader so I have to load into an XmlDocument to perform the validation. I also cannot just load the SqlXml straight to XmlDocument--I would have to do extra type conversions.

Am I missing something or is that just the way it is?

[2] I hate that I am using a shared member to pass the validation result from the event handler and then back to the caller. This is fine in my first specific usage where I know there is a sequence of individual calls. But if I ever tried using this with multiple callers or in set operation I fear the results would be suspect.

Is there a way around this?

Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Imports System.Xml
Imports System.Xml.Schema

Partial Public Class UserDefinedFunctions
  'this is from an ms sample
  Const TARGET_NAMESPACE As String = "http://www.contoso.com/books" 
  Const SCHEMA_URI As String = "D:\temp\temp.xsd"

  Shared schemaSet As XmlSchemaSet
  Shared schemaValidationEventHandler As ValidationEventHandler
  Shared isValid As Boolean
  Shared doc As XmlDocument

  Shared Sub New()
    schemaSet = New XmlSchemaSet
    schemaSet.Add(TARGET_NAMESPACE, SCHEMA_URI)
    schemaSet.Compile()

    doc = New XmlDocument
    doc.Schemas = schemaSet

    schemaValidationEventHandler = New ValidationEventHandler(
                                   AddressOf ValidationCallBack)
  End Sub

  <SqlFunction()> _
  Public Shared Function ValidateWithContosoXsd(ByVal xml As SqlXml) _
                                                         As SqlBoolean
    isValid = True

    Dim reader As XmlReader = xml.CreateReader
    reader.Settings.ValidationType = ValidationType.Schema

    doc.Load(reader)
    doc.Validate(schemaValidationEventHandler)

    ValidateWithContosoXsd = isValid
  End Function

  Private Shared Sub ValidationCallBack(ByVal sender As Object,
                                        ByVal args As ValidationEventArgs)
    isValid = False
  End Sub
End Class

I did follow a clue to try using the initial reader as the basis a for a second reader, which works in a roughly equivalent console app. The difference in the console app is that the first ready is from a file uri instead of from SqlXml. Sadly, this always shows valid when done in the clr.

Partial Public Class UserDefinedFunctions

    Const TARGET_NAMESPACE As String = "http://www.contoso.com/books"
    Const SCHEMA_URI As String = "D:\temp\temp.xsd"

    Shared settings As XmlReaderSettings
    Shared schemaSet As XmlSchemaSet
    Shared schemaValidationEventHandler As ValidationEventHandler
    Shared isValid As Boolean

    Shared Sub New()

        schemaSet = New XmlSchemaSet
        schemaSet.Add(TARGET_NAMESPACE, SCHEMA_URI)
        schemaSet.Compile()

        schemaValidationEventHandler = New ValidationEventHandler(AddressOf ValidationCallBack)

        settings = New XmlReaderSettings
        settings.Schemas = schemaSet
        settings.ValidationType = ValidationType.Schema
        AddHandler settings.ValidationEventHandler, schemaValidationEventHandler

    End Sub

    <SqlFunction()> _
    Public Shared Function ValidateWithContosoXsd(ByVal xml As SqlXml) As SqlBoolean

        isValid = True

        Dim baseReader As XmlReader = xml.CreateReader
        Dim reader As XmlReader = XmlReader.Create(baseReader, settings)

        While reader.Read()
        End While

        ValidateWithContosoXsd = isValid

    End Function

    Private Shared Sub ValidationCallBack(ByVal sender As Object, ByVal args As ValidationEventArgs)
        isValid = False
    End Sub
End Class
Was it helpful?

Solution

I'm still hoping ideas on the second question, but I can answer the first part of the question. Perhaps I should split that into a separate question.

[EDIT: figured out 2nd question as well. See below.]

I was somehow masking an obscure error: System.InvalidOperationException: Cannot change conformance checking to Document. Make sure the ConformanceLevel in XmlReaderSettings is set to Auto for wrapping scenarios.

Resolving that leads to the working code below. It also has some other changes, but the keys is settings.ConformanceLevel = ConformanceLevel.Auto.

<SqlFunction()> _
Public Shared Function ResetSchema(ByVal targetNameSpace As SqlString, ByVal schemaUri As SqlString) As Boolean
    Dim result As Boolean

    Try
        schemaSet = New XmlSchemaSet
        schemaSet.Add(targetNameSpace, schemaUri)
        schemaSet.Compile()

        settings = New XmlReaderSettings
        settings.Schemas = schemaSet
        settings.ValidationType = ValidationType.Schema
        settings.ConformanceLevel = ConformanceLevel.Auto
        settings.ValidationFlags = settings.ValidationFlags Or XmlSchemaValidationFlags.ReportValidationWarnings
        AddHandler settings.ValidationEventHandler, AddressOf ValidationCallBack

        result = True
    Catch
        'result = False
    End Try

    ResetSchema = result
End Function

Public Shared Function ValidateWithXsd(ByVal xml As SqlXml) As SqlBoolean
    Try

        isValid = True

        Dim reader As XmlReader = xml.CreateReader
        Dim validatingReader As XmlReader = XmlReader.Create(reader, settings)

        While validatingReader.Read
        End While

    Catch ex As Exception
        Throw ex
    Finally
        ValidateWithXsd = isValid
    End Try

End Function

Private Shared Sub ValidationCallBack(ByVal sender As Object, ByVal args As ValidationEventArgs)
    isValid = False
End Sub

As for the second question, I moved the actual validation to a 2nd friend class. I still keep the schema cached, but can do set based operation with the function.

I won't post the changes to the primary class as they are pretty trivial, but the new class:

Friend Class SchemaValidator

Private isValid As Boolean

Friend Function Validate(ByVal baseReader As XmlReader, ByVal settings As XmlReaderSettings) As Boolean
    Try

        isValid = True
        AddHandler settings.ValidationEventHandler, AddressOf ValidationEventHandler
        Dim validatingReader As XmlReader = XmlReader.Create(baseReader, settings)

        While validatingReader.Read
        End While

    Catch ex As Exception
        Throw ex
    Finally
        RemoveHandler settings.ValidationEventHandler, AddressOf ValidationEventHandler
        Validate = isValid
    End Try

End Function

Private Sub ValidationEventHandler(ByVal sender As Object, ByVal args As ValidationEventArgs)
    isValid = False
End Sub
End Class
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top