1

I have a VB.NET Function that Validates my XML vs a schema and then if it fails outputs the failure to an XML response for the Web Service.

The issue I have is when I said correctly formatted XML it validates and is successful, then if I send incorrect XML the validator correct states where it is failing in validation.

If I THEN send the correct XML that is formatted correctly through the Web Service the my Validation Check function returns the same error that it experienced when checking the previous XML, almost as if its 'caching' the issue.

Can anyone tell me why this is the case, I can confirm the XML being passed through the validation is 100% correct as I've viewed it and exported to another program of mine outside of VB.NET that validates the XML.

My ValidateXML Function

Public Shared Function ValidateXML(xmlFilePath As String) As String

    Dim doc As New XmlDocument()
    doc.LoadXml(xmlFilePath)
    doc.Schemas.Add(Nothing, "http://www.fresh.co.uk/freshit/fresh_lead_schema.xsd")
    Dim errorBuilder As New XmlValidationErrorBuilder()
    doc.Validate(New ValidationEventHandler(AddressOf XmlValidationErrorBuilder.ValidationEventHandler))
    FIGCloud.errorsText = XmlValidationErrorBuilder.GetErrors()
    If FIGCloud.errorsText IsNot Nothing Then
        'Throw New Exception(errorsText)
        Return "Failed"
    Else
        Dim ReturnText As String = doc.InnerXml 'Returns XML Document as a String

        Return ReturnText
    End If

End Function

My XMLvalidationErrorBuilder Class I took from another source

Public Class XmlValidationErrorBuilder
Public Shared _errors As New List(Of ValidationEventArgs)()

Public Shared Sub ValidationEventHandler(ByVal sender As Object, ByVal args As ValidationEventArgs)
    If args.Severity = XmlSeverityType.Error Then
        _errors.Add(args)
    End If
End Sub

Public Shared Function GetErrors() As String
    If _errors.Count <> 0 Then
        Dim builder As New StringBuilder()
        builder.Append("The following ")
        builder.Append(_errors.Count.ToString())
        builder.AppendLine(" error(s) were found while validating the XML document against the XSD:")
        For Each i As ValidationEventArgs In _errors
            builder.Append("* ")
            builder.AppendLine(i.Message)
        Next
        Return builder.ToString()
    Else
        Return Nothing
    End If
End Function
End Class

Here is the correct formatted XML

<FreshLead>
<ContactDetails>
    <Title>Mr</Title>
    <FirstName>Joe</FirstName>
    <LastName>Bloggs</LastName>
    <EmailAddress>joe.bloggs@davisco.co.uk</EmailAddress>
    <HomeTel>01527 321 850</HomeTel>
    <MobileTel>07771111111</MobileTel>
    <DateOfBirth>21/05/1987</DateOfBirth>
    <Address>Joe Bloggs Road, Here</Address>
    <Postcode>B98 9PA</Postcode>
</ContactDetails>
<TradeDetails>
    <TradingName>Motor Trade Direct</TradingName>
    <TypeOfBus>Sales</TypeOfBus>
    <AgeOfBus>5 Years</AgeOfBus>
    <NoOfEmployees>5</NoOfEmployees>
    <NoOfDrivers>5</NoOfDrivers>
    <FullPartTime>Full-Time</FullPartTime>
    <BusPostcode>B98 9PA</BusPostcode>
    <BusPremises>No</BusPremises>
    <DemoCover>No</DemoCover>
    <PremIndem>10000</PremIndem>
    <RoadRisksIndem>20000</RoadRisksIndem>
    <VolXS>250</VolXS>
    <CoverStart>19/03/2015</CoverStart>
    <CoverReq>Comprehensive</CoverReq>
    <MTExperience>5 Years</MTExperience>
</TradeDetails>
<ProposerDetails>
    <UKResident>5 Years</UKResident>
    <FullUKLic>10 Years+</FullUKLic>
    <MT.NCB>2 Years</MT.NCB>
    <PC.NCB>2 Years</PC.NCB>
    <ClaimsConv5yr>No</ClaimsConv5yr>
</ProposerDetails>
<AddDetails>
    <CurrInsurer>Markerstudy</CurrInsurer>
    <BestQuote>2000.00</BestQuote>
    <ContactTime>Evening</ContactTime>
    <PrefContact>Email</PrefContact>
</AddDetails>

This validates correctly when sending through the Web Service the first time.

I have just changed 2 of the fields so that they contain 'Strings' within 'Int16' fields and this then fails (as it should) these fields are the 'Indem' fields.

<FreshLead>
<ContactDetails>
    <Title>Mr</Title>
    <FirstName>Joe</FirstName>
    <LastName>Bloggs</LastName>
    <EmailAddress>joe.bloggs@davisco.co.uk</EmailAddress>
    <HomeTel>01527 321 850</HomeTel>
    <MobileTel>07771111111</MobileTel>
    <DateOfBirth>21/05/1987</DateOfBirth>
    <Address>Joe Bloggs Road, Here</Address>
    <Postcode>B98 9PA</Postcode>
</ContactDetails>
<TradeDetails>
    <TradingName>Motor Trade Direct</TradingName>
    <TypeOfBus>Sales</TypeOfBus>
    <AgeOfBus>5 Years</AgeOfBus>
    <NoOfEmployees>5</NoOfEmployees>
    <NoOfDrivers>5</NoOfDrivers>
    <FullPartTime>Full-Time</FullPartTime>
    <BusPostcode>B98 9PA</BusPostcode>
    <BusPremises>No</BusPremises>
    <DemoCover>No</DemoCover>
    <PremIndem>Over 10000</PremIndem>
    <RoadRisksIndem>Over 20000</RoadRisksIndem>
    <VolXS>250</VolXS>
    <CoverStart>19/03/2015</CoverStart>
    <CoverReq>Comprehensive</CoverReq>
    <MTExperience>5 Years</MTExperience>
</TradeDetails>
<ProposerDetails>
    <UKResident>5 Years</UKResident>
    <FullUKLic>10 Years+</FullUKLic>
    <MT.NCB>2 Years</MT.NCB>
    <PC.NCB>2 Years</PC.NCB>
    <ClaimsConv5yr>No</ClaimsConv5yr>
</ProposerDetails>
<AddDetails>
    <CurrInsurer>Markerstudy</CurrInsurer>
    <BestQuote>2000.00</BestQuote>
    <ContactTime>Evening</ContactTime>
    <PrefContact>Email</PrefContact>
</AddDetails>

But when I pass the first working example back through it fails with the same errors as the above XML, I have provided the location of the schema it validates against in my code, for anyone that wishes to use a working example and figure out where I'm going wrong.

Thanks

Lynchie
  • 1,077
  • 2
  • 20
  • 36

2 Answers2

3

In my original example, the purpose of the XmlValidationErrorBuilder class was to provide stateful objects where each one could be used to store the list of errors for one single operation. Each time a validation was to be performed, it would create a new instance of the XmlValidationErrorBuilder class and then use that local object to store all of the errors just for that one operation. Once the validation operation was complete, the errors would be read from it and it would be cast aside. In other words, each validation operation kept a separate list of it's own errors in its own local XmlValidationErrorBuilder object.

The problem in your solution is that you have changed several of the members from instance members to Shared members. For instance, in my original example, the XmlValidationErrorBuilder class looked like this:

Public Class XmlValidationErrorBuilder
    Private _errors As New List(Of ValidationEventArgs)()

    Public Sub ValidationEventHandler(ByVal sender As Object, ByVal args As ValidationEventArgs)
        If args.Severity = XmlSeverityType.Error Then
            _errors.Add(args)
        End If
    End Sub

    Public Function GetErrors() As String
        If _errors.Count <> 0 Then
            Dim builder As New StringBuilder()
            builder.Append("The following ")
            builder.Append(_errors.Count.ToString())
            builder.AppendLine(" error(s) were found while validating the XML document against the XSD:")
            For Each i As ValidationEventArgs In _errors
                builder.Append("* ")
                builder.AppendLine(i.Message)
            Next
            Return builder.ToString()
        Else
            Return Nothing
        End If
    End Sub
End Class

But in your solution, you changed the two methods and the _errors field to Shared members. That means that all calls to the error builder will always work on the same list of errors, regardless of which validation operation is using it.

Also, in my original example, I passed the delegate to the error builder's method like this:

Dim errorBuilder As New XmlValidationErrorBuilder()
doc.Validate(New ValidationEventHandler(AddressOf errorBuilder.ValidationEventHandler))

As you can see, that creates a whole new local instance of the error builder, and passes the reference to its method to the Validate method. However, in your solution, you pass a delegate to the Shared method like this:

doc.Validate(New ValidationEventHandler(AddressOf XmlValidationErrorBuilder.ValidationEventHandler))

So in your solution, every time you call Validate, you are passing it a delegate to the same Shared method which then uses a single Shared field to store the errors. Any time you declare a field as Shared, it essentially creates global state. The only difference between Global and Private Shared fields is the level of accessibility, but as long as they are accessible, they still work the same way and serve the same purpose. In other words, think of Private Shared as a private global variable. So, you could say that the reason why your solution acts "as if its 'caching' the issue," is because it is caching the issue in what is essentially a global variable.

Community
  • 1
  • 1
Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • 1
    Top man, thank you so much for a very informative answer, definitely teaches me a thing or two! :) – Lynchie Oct 29 '15 at 13:03
0

I ended up simplifying my code to the following

Public Shared Function ValidateXMLv2(ByVal strXML As String, ByVal xsdPath As String) As Boolean
    Try

        Dim schema As XmlReader = XmlReader.Create(xsdPath)
        Dim document As XmlDocument = New XmlDocument()
        document.LoadXml(strXML)
        document.Schemas.Add("", schema)

        Dim eventHandler As ValidationEventHandler = New ValidationEventHandler(AddressOf ValidationEventHandler)

        document.Validate(AddressOf ValidationEventHandler)

        ' the following call to Validate succeeds.
        If FIGCloud.validationerrors = "" Then
            Return True
        Else
            Return False
        End If

    Catch ex As Exception
        MsgBox(ex.Message & Environment.NewLine & ex.StackTrace)
        Console.WriteLine(ex.Message & Environment.NewLine & ex.StackTrace)
        Throw
    End Try
End Function

Public Shared Sub ValidationEventHandler(ByVal sender As Object, ByVal e As ValidationEventArgs)
    FIGCloud.validationerrors += e.Message & Environment.NewLine
End Sub

Then outside of the function I set FIGCloud.validationerrors to "".

Lynchie
  • 1,077
  • 2
  • 20
  • 36