Upload an XML File and Validate Against a Schema...
By: John Kilgo Date: November 15, 2002 Download the code.


This ASP.Net (VB) program allows you to locate an XML file on your local machine, or anywhere on your network for that matter, upload it to a web server and validate the file against an XML Schema existing on the web server. If the xml file validates, a success notification is provided. If the validation fails, either because your xml file is malformed, or because the data does not match the requirements listed in your schema, an html file will be produced containing a table of errors and the error locations.

If you donwload this project you will find an example xml file referencing a schema on this web site. You may use this for testing. Experiment with the xml file, introducing errors to see the output of the program. For your own use, you must point your xml file to a schema on your web server, or somewhere on your network.

The first program shown is the .aspx file which implements the file upload. Control is then turned over to the code behind page.

<%
' Program: XmlValidateCB1.aspx
' By: John Kilgo
' Date: October 21, 2002
' CodeBehind: XmlValidatCB1.aspx.vb
' Purpose: Uploads an XML file and validates against a
' Schema. Produces an html file showing errors if any.
%>
<%@Page Inherits="XmlValidateCB1" Src="XmlValidateCB1.aspx.vb" %>
<% Response.Expires = -1444 %>
<HTML>
<META HTTP-EQUIV="Pragma" CONTENT='no-cache'>
<HEAD>
<TITLE>XmlValidateCB1.aspx</TITLE>
</HEAD>
<BODY>
<FORM enctype="multipart/form-data" runat="server" ID="Form1">
Select File to Validate:
<INPUT type="file"
    id="txtSelectedFile"
    runat="server"
    NAME="txtSelectedFile">
<P>
<INPUT type="button"
    id="btnValidate"
    value="Validate"
       OnServerClick="btnValidate_Click"
    Runat="server"
    NAME="validate">
</P>
<asp:Label ID="lblMessage"
        Runat="server" />
</FORM>
</BODY>
</HTML>
Next is the code behind file XmlValidateCB1.aspx.vb. Please note that the guts of this code was taken from the book "ASP.NET: Tips, Tutorials and Code". I changed it from C# to VB and added to it considerably, but I want to give credit where credit is due. First, the Namespaces involved:
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls
Imports System.IO
Imports System.IO.FileStream
Imports System.Text
Imports System.Xml
Imports System.Xml.Schema
Next is the main class declaration and the Click event code for the button on the .aspx page. Notice that most of the code is in a Try-Catch block. This will catch some, but not all files the user may select which are not XML files. The Session ID is used to build a unique file name for the errors page if needed. Since the same Session ID gets used much more often than I thought it would, the "unique" file named is deleted before it is used. If the file does not exist the File.Delete fails silently. The XML file gets saved in a specific directory, "d:\xmluploads" in my case. You may need to change that directory to suit your needs. "ValidatorVB" is a class defined a little further down in the code, so an object for it gets created in this section and the object is called. A boolean variable holds the return from the object. It is then evaluated to print a success or fail message for the user. The fail message includes a link to the html file containing the table of error messages and locations.
Public Class XmlValidateCB1 : Inherits Page
  Protected txtSelectedFile As HtmlInputFile
  Protected lblMessage As Label
  Sub btnValidate_Click(source As Object, e As EventArgs)
    If Not (txtSelectedFile.PostedFile.FileName = "") Then
      Try
        Dim strFilePath As String
        Dim postedFile = txtSelectedFile.PostedFile
        Dim strFileName As String = Path.GetFileName(postedFile.FileName)
        Dim strSession As String = Session.SessionID
        Dim blnStatus as Boolean
        Dim strLogFile As String = Server.MapPath(strSession & ".htm")
        postedFile.SaveAs("d:/xmluploads/" & strFileName)
        strFilePath = "d:/xmluploads/" & strFileName
        File.Delete(Server.MapPath(strSession & ".htm"))
        Dim objValidate As ValidatorVB = New ValidatorVB( _
          strFilePath, strLogFile, True)

        'Call method to process XML document
        blnStatus = objValidate.Validate()
        If (blnStatus) Then
          lblMessage.Text = "Validation of " & strFileName & " was SUCCESSFUL!"
        Else
          lblMessage.Text = "Validation of " & strFileName & " failed! Check the
                          log file for information on the failure
                          (< a href=http://www.dotnetjohn/" & strSession & ".htm" &
                            ">View Errors</a>)."
        End If
      Catch exc As Exception
        lblMessage.Text = "Failed validating file"
      End Try
    End If
  End Sub ' btnValidate_Click 
Next is the ValidatorVB class. Most of the variables to read the XML file and do the actual validation are done here, and a Sub New is used to instantiate the class.
  Public Class ValidatorVB
    Dim blnHasDataError As Boolean = False
    Dim blnIsValid As boolean = True
    Dim _logError As boolean = True
    Dim _logFile As string = ""
    Dim _xmlFilePath As string = ""
    Dim xmlReader As XmlTextReader
    Dim validatingReader As XmlValidatingReader
    Dim validHandler As ValidationEventHandler
    Dim strHtml As String
    Dim blnFirst As Boolean = True

    Public Sub New(strFilePath As String, strLogFile As String, blnLogError As Boolean)
      _xmlFilePath = strFilePath
      _logFile = strLogFile
      _logError = blnLogError
    End Sub
The class continues with a public function to do the actual validation. An XmlTextReader and a XmlValidating reader are created and the ValidationType is set to a schema. A ValidationEventHandler is also instantiated. The code for the above, as well as for reading the XML file are contained within a Try block. Two Catch blocks are used in order to catch both mal-formed xml and bad data according to the schema. A Finally block is used to close the readers. The variable blnHasDataError is checked to see to see if the error was bad data per the schema vs. missing or inconsistent xml tags. If the error was due to the xml file being mal-formed, that error message is created immediately and processing stops. If the problem was bad data per the schema, then the error message(s) are generated in the validation call back code.
    Public Function Validate() As Boolean
    Dim strErrorMsg As String
      Try
        xmlReader = new XmlTextReader(_xmlFilePath)
        validatingReader = new XmlValidatingReader(xmlReader)
        validatingReader.ValidationType = ValidationType.Schema
        validHandler = new ValidationEventHandler(addressof ValidationCallBack)
        AddHandler validatingReader.ValidationEventHandler, validHandler
        'Parse through XML
        While (validatingReader.Read())

        End While
      Catch a As UnauthorizedAccessException
        strErrorMsg = a.Message
      Catch a As Exception
        strErrorMsg = a.Message
        blnIsValid = False
      Finally
        'Close readers
        If (xmlReader.ReadState <> ReadState.Closed) Then
          xmlReader.Close()
        End If
        If (validatingReader.ReadState <> ReadState.Closed) Then
          validatingReader.Close()
        End If
      End Try
      If blnHasDataError = False Then
        Dim writer as StreamWriter
        writer = new StreamWriter(_logFile, true, Encoding.ASCII)
        writer.WriteLine("<html><head><title>ValidationLog</title></head>")
        writer.WriteLine("<body>")
        writer.WriteLine("<table border=1>")
        writer.WriteLine("<th>")
        writer.WriteLine("Validation error in: " & _xmlFilePath)
        writer.WriteLine("</th>")
        writer.WriteLine("<tr bgcolor=#6666CC>")
        writer.WriteLine("<td><font color=white><b>Error Message _
          </b></font></td>")
        writer.WriteLine("</tr>")
        writer.WriteLine("<tr bgcolor=D5CCBB>")
        writer.WriteLine("<td width=100%>")
        writer.WriteLine(strErrorMsg)
        writer.WriteLine("</td>")
        writer.WriteLine("</tr>")
        writer.Flush()
        writer.Close()
      End If
      Return blnIsValid
    End Function ' Validate()
The next and last section is the ValidationCallBack and the ending of the master class. If processing hits this section then there is bad data per the schema contained in the XML file. Since there may be more than one instance of bad data a boolean variable is used to create the opening html and html table creation only once. Without some mechanism such as this, the html document headings and the table could be created more than once resulting in a pretty ugly html file. A StreamWriter is used to write out the html for the error log. The stream writer is closed in the Finally block.
    Private Sub ValidationCallBack(sender as object, args as ValidationEventArgs)
      blnIsValid = False  'hit callback so document has a problem
      Dim writer as StreamWriter

      Try
        If (_logError) Then
          blnHasDataError = True
          If blnFirst Then
            writer = new StreamWriter(_logFile, true, Encoding.ASCII)
            writer.WriteLine("<html><head><title>ValidationLog</title></head>")
            writer.WriteLine("<body>")
            writer.WriteLine("<table border=1>")
            writer.WriteLine("<th colspan=2>")
            writer.WriteLine("Validation error in: " & _xmlFilePath)
            writer.WriteLine("</th>")
            writer.WriteLine("<tr bgcolor=#6666CC>")
            writer.WriteLine("<td><font color=white><b>Error Message</b>
              </font></td>")
            writer.WriteLine("<td><font color=white><b>Error Location</b>
              </font></td>")
            writer.WriteLine("</tr>")
            writer.Flush()
            writer.Close()
            blnFirst = False 'lay out table only on the first pass
          End If
          writer = new StreamWriter(_logFile, true, Encoding.ASCII)
          writer.WriteLine("<tr bgcolor=D5CCBB>")
          writer.WriteLine("<td width=80%>")
          writer.WriteLine(args.Message)
          writer.WriteLine("</td>")
          If (xmlReader.LineNumber > 0) Then
            writer.WriteLine("<td width=20%>")
            writer.WriteLine("Line: " & xmlReader.LineNumber &
              " Position: " & xmlReader.LinePosition)
            writer.WriteLine("</td>")
          End If
          writer.WriteLine("</tr>")
        End If
        writer.Flush()
      Catch
      Finally
        'Close writer
        If (NOT writer Is Nothing) Then
          writer.Close()
        End If
      End Try
    End Sub  ' ValidationCallBack()
  End Class  ' ValidatorVB

End Class ' XmlValidateCB1
Conclusion: In this article we have covered uploading an XML file from your file system to a directory on a web server, saved the file in a specific location, validated the file against a schema loaded on the web server, and created an error log if any type of error was found in the XML file. You have seen the use of a Validating Reader, and the use of Try-Catch-Finally blocks to catch various errors the XML file may contain.

You may download the .aspx and the .aspx.vb files as well as an example XML file and its associated schema (.xsd) file.

Download the files by clicking Here.