BUG: The XmlValidatingReader class does not close the stream when the XML document references a DTD that has errors (816221)



The information in this article applies to:

  • Microsoft .NET Framework 1.0
  • Microsoft Windows .NET Framework 1.1
  • Microsoft .NET Framework Class Libraries 1.0
  • Microsoft Windows .NET Framework Class Libraries 1.1
  • Microsoft Visual C# .NET (2002)
  • Microsoft Visual C# .NET (2003)
  • Microsoft Visual Basic .NET (2002)
  • Microsoft Visual Basic .NET (2003)

SYMPTOMS

When you validate your XML document by using the XmlValidatingReader class with respect to an external document type definition (DTD), and the DTD has an error, the stream that is opened by XmlValidatingReader to load the DTD is not closed. Therefore, when you try to open the DTD with write permissions, you receive the following error message:
An unhandled exception of type 'System.IO.IOException' occurred in mscorlib.dll
Additional information: The process cannot access the file "DTDFileName" because it is being used by another process.

CAUSE

When you use XmlValidatingReader to validate an XML document with respect to DTD, the .NET Framework internally opens a stream to read the DTD. When the error occurs while validating the XML document, the stream that is used to read the DTD is not closed, and you receive the error message that is listed in the "Symptoms" section.

WORKAROUND

To work around this problem, use the garbage collector (GarbageCollector.exe) to reclaim the unused memory and to close the stream. To do this, add the following code in the beginning of the method where the DTD is being corrected.

Visual C# .NET Code

// This method call triggers the garbage collector
//  to collect the unreferenced memory.
GC.Collect();
// Wait for the GC's Finalize thread to finish 
// executing all queued Finalize methods.
GC.WaitForPendingFinalizers(); 

Visual Basic .NET Code

' This method call triggers the garbage collector
'  to collect the unreferenced memory.
GC.Collect()
' Wait for the GC's Finalize thread to finish 
' executing all queued Finalize methods.
GC.WaitForPendingFinalizers()
For more information about the code to add to the RemoveTheErrorInDTD method, see the "More Information" section.

STATUS

Microsoft has confirmed that this is a problem in the Microsoft products that are listed at the beginning of this article.

MORE INFORMATION

Steps to Reproduce the Behavior
  1. Save the following XML document in a text file as C:\Xmlfile.xml:
    <?xml version="1.0" encoding="utf-8" ?>
    <!DOCTYPE CompanyEmployees SYSTEM "C:/XMLDTD.dtd">
    <CompanyEmployees>
       <Employee>
          <EmpId>10001</EmpId>
          <Name>
             <First>John</First>
             <Middle>Sam</Middle>
             <Last>Desouza</Last>
          </Name>
          <Address>
             <Address1>408, Lake Apartments</Address1>
             <Address2>Rock Island</Address2>
             <City>Rock City</City>
             <Pin>10101</Pin>
          </Address>
          <Phone>
             <Residence>+1-435-89734</Residence>
             <Work>+1-436-32879</Work>
             <Mobile>+1-4358972922</Mobile>
             <Fax>+1-435-89729</Fax>
          </Phone>
       </Employee>
    </CompanyEmployees>
  2. Save the following DTD in a text file as C:\Xmldtd.dtd:
    <!ELEMENT CompanyEmployees (Employee)+ >
    <!ELEMENT Employee (EmpId,Name, Address, Phone)>
    <!ELEMENT EmpId (#PCDATA)>
    <!ELEMENT Name (First, Middle*, Last)+>
    <!ELEMENT Middle (#PCDATA)>
    <!ELEMENT Last (#PCDATA)>
    <!ELEMENT Address (Address1, Address2*, City, Pin)>
    <!ELEMENT Address1 (#PCDATA)>
    <!ELEMENT Address2 (#PCDATA)> 
    <!ELEMENT City (#PCDATA)>
    <!ELEMENT Pin (#PCDATA)@
    <!ELEMENT Phone (Residence, Work, Mobile*, Fax*)>
    <!ELEMENT Residence (#PCDATA)>
    <!ELEMENT Work (#PCDATA)> 
    <!ELEMENT Mobile (#PCDATA)>
    <!ELEMENT Fax (#PCDATA)>
    Note An error in this DTD at line 11, column 24 (the "@" character) causes a compilation error and the symptoms that are mentioned in this article.
  3. Start Microsoft Visual Studio .NET.
  4. On the File menu, point to New and then click Project.
  5. Under Project Types, select either Visual C# .NET or Visual Basic .NET. Under Templates, select Console Application.
  6. Name the project MyConsoleApplication, and then click OK.
  7. Replace the existing code with the following code (either Visual C# .NET or Visual Basic .NET, as appropriate):

    Visual C# .NET Code

    using System;
    using System.Xml;
    using System.IO;
    
    
    namespace MyConsoleApplication
    {
       class MyTestClass
       {
          private string dtdFile;  // Absolute path of the DTD file
          private string xmlFile;  // Absolute path of the XML Document
    
          // No default constructor. 
          public MyTestClass(string dtdFile, string xmlFile)
          {
             this.dtdFile = dtdFile;
             this.xmlFile = xmlFile;
          }
          
          // This method reads the DTD and replaces the invalid
          // "@" character with the ">" character.
          public void RemoveTheErrorInDTD()
          {
             StreamReader sr = File.OpenText(dtdFile);
             string data = sr.ReadToEnd();
             sr.Close();
    
             char[] charData = new Char[data.Length];
             int index = data.IndexOf('@');
             charData = data.ToCharArray();
             charData[index] = '>';
    
             StreamWriter sw = File.CreateText(dtdFile);
             sw.Write(new String (charData));
             sw.Close();
          }
          
          // Validating the XML with respect to the external DTD 
          // that was given in the XML document.
          public bool ValidateTheXMLDocWithDTD(bool validate)
          {
             XmlTextReader tReader = null; 
             XmlValidatingReader vReader = null; 
             try 
             { 
                FileStream stream = new FileStream(xmlFile, FileMode.Open,FileAccess.Read);
                tReader = new XmlTextReader (stream); 
                tReader.WhitespaceHandling = WhitespaceHandling.Significant; 
                vReader = new XmlValidatingReader(tReader); 
                vReader.EntityHandling = EntityHandling.ExpandCharEntities; 
    
                if (!validate) 
                   vReader.ValidationType = ValidationType.None; 
                else 
                   vReader.ValidationType = ValidationType.XDR;
    
                while (vReader.Read())
                {
                   // Reading the text nodes and displaying on the console
                   if ( vReader.NodeType == XmlNodeType.Text )
                      Console.WriteLine(vReader.Value+"\n");
                }
                return true; 
             } 
             catch (Exception e) 
             { 
                Console.WriteLine("XML Error: "+e.Message); 
                return false; 
             } 
             finally 
             { 
                // Finally block. Here XmlValidatingReader and
                // XmlTextReader will get closed.
                if (vReader != null) 
                { 
                   vReader.Close(); 
                }
                if (tReader != null) 
                { 
                   tReader.Close(); 
                }
                Console.Read(); 
             } 
    
          }
    
          // Main Method.
          static void Main(string[] args)
          {
             // Creating the object of MyTestClass.
             MyTestClass testObj = new MyTestClass(@"C:\XMLDTD.dtd", @"C:\XMLFile1.xml");
    
             // Validating the XML document.
             bool x = testObj.ValidateTheXMLDocWithDTD(true);
             if ( x == false ) // If validation is not successful
             {
                testObj.RemoveTheErrorInDTD(); // correcting the error
                bool y = testObj.ValidateTheXMLDocWithDTD(true); // Validating it again
             }
    
             Console.Read(); 
          }
       }
    }
    

    Visual Basic .NET Code

    Imports System
    Imports System.Xml
    Imports System.IO
    
    Public Class MyTestClass
       Private dtdFile As String 'Absolute path if the DTD File
       Private xmlFile As String 'Absolute path if the XML Document
    
       'No default constructor. 
       Public Sub New(ByVal dtdFile As String, ByVal xmlFile As String)
          Me.dtdFile = dtdFile
          Me.xmlFile = xmlFile
       End Sub
    
       '      This method reads the DTD and replaces the invalid
       '      "@" character with the ">" character.
       Public Sub RemoveTheErrorInDTD()
          Dim sr As StreamReader
          Dim data As String
          sr = File.OpenText(dtdFile)
          data = sr.ReadToEnd()
          sr.Close()
          Dim index As Long
          index = data.IndexOf("@")
          Dim charArray As Char()
          charArray = data.ToCharArray()
          charArray(index) = ">"
    
          Dim sw As StreamWriter
          sw = File.CreateText(dtdFile)
          sw.Write(New String(charArray))
          sw.Close()
       End Sub
    
       ' Validating the XML with respect to the external DTD 
       ' that was given in the XML document.
       Public Function ValidateTheXMLDocWithDTD(ByVal validate As Boolean) As Boolean
          Dim tReader As XmlTextReader
          Dim vReader As XmlValidatingReader
          Try
             Dim stream As New FileStream(xmlFile, FileMode.Open, FileAccess.Read)
             tReader = New XmlTextReader(Stream)
             tReader.WhitespaceHandling = WhitespaceHandling.Significant
             vReader = New XmlValidatingReader(tReader)
             vReader.EntityHandling = EntityHandling.ExpandCharEntities
             If validate = False Then
                vReader.ValidationType = ValidationType.None
             Else
                vReader.ValidationType = ValidationType.XDR
             End If
             While (vReader.Read())
                If vReader.NodeType = XmlNodeType.Text Then
                   Console.WriteLine(vReader.Value + "\n")
                End If
             End While
             Return True
          Catch excep As Exception
             Console.WriteLine("XML Error: " & excep.Message)
          Finally
             If Not vReader Is Nothing Then
                vReader.Close()
             End If
             If Not tReader Is Nothing Then
                tReader.Close()
             End If
             Console.Read()
          End Try
    
       End Function
    End Class
    
    Module Module1
       Sub Main()
          ' Creating the object of MyTestClass.
          Dim testObj As New MyTestClass("C:\XMLDTD.dtd", "C:\XMLFile1.xml")
          Dim validated As Boolean
          validated = False
          While validated = False
             ' Validating the XML Document; validation continues until validated as True.
             validated = testObj.ValidateTheXMLDocWithDTD(True)
             If validated = False Then  'If validation is not successfull
                testObj.RemoveTheErrorInDTD() ' correcting the error
             End If
          End While
          Console.Read()
       End Sub
    
    End Module
    
  8. On the Debug menu, click Start.

    During the correction of the error in the Xmldtd.dtd file, you receive the exception that is mentioned in the "Symptoms" section.

REFERENCES

For more information about XML, visit the following MSDN Web sites:For more information about garbage collection, visit the following MSDN Web site:

Modification Type:MajorLast Reviewed:4/26/2006
Keywords:kbvs2002sp1sweep kbBug kberrmsg kbGarbageCollect kbXML kbValidation kbFileIO KB816221 kbAudDeveloper