Geeks With Blogs
Chris Falter .NET Design and Best Practices

The past couple of days I've been working on a command-line config file parser that will enable our build process to emit the correct web.config for any given environment.  For example, we define environments called “model,” “fqa” (final qa) and “prod” (production).  The idea is to embed the environment-specific settings in comments at the end of the web.config file, then run the parser, passing in the environment name as a command-line parameter.  Here is a sample of what the environment-specific settings might look like:

<!-- [PROD]
<configuration>
    <system.web>
        <customErrors mode="On" defaultRedirect="Error.htm" replaceHere="true"/>
        <authentication >
            <forms loginUrl="login.aspx" name="PROD" timeout="60" path="/" replaceHere="true">
            </forms>
        </authentication>
        <httpCookies httpOnlyCookies="false" requireSSL="false" domain="seibelsonline.com"  replaceHere="true"/>
    </system.web>
</configuration>
-->

<!--
[FQA]
<configuration>
    <system.web>
        <customErrors mode="RemoteOnly" defaultRedirect="Error.htm" replaceHere="true"/>
        <authentication >
            <forms loginUrl="login.aspx" name="FQA" timeout="60" path="/" replaceHere="true">
            </forms>
        </authentication>
    </system.web>
</configuration>
-->


The replaceHere="true" attribute marks the location at which the parser is supposed to insert a replacement node.  Once I have the code fully debugged, I hope to be able to post it here.  (Please leave a comment if you are interested in seeing it.)

If you're like me, you check the Microsoft QuickStart samples when you want to do a task for the first time.  The XML file samples Microsoft provides are quite straightforward.  To read the document, you call the XmlDocument.Load(fileName) method; to save it, you simply call the XmlDocument.Save(fileName) method.  So to read, modify, and save the document, you should be able to write code like this:

string fileName = args[0];

// load the XML document
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(fileName);

// TODO: parse the special comments and modify the in-memory document

// write the document back to the file system
xmlDoc.Save(fileName);


Wouldn't life be wonderful if code this simple would just work?  Well, as my kids are probably tired of hearing, life is often unfair.  When your code tries to call xmlDoc.Save(), it will throw a System.IOException in a pique of outrage.  The exception message will be something like “another process has the file open.”

What? Another process?  I'm looking at my code, and all I see is one process!  How can another process have it open?  Well, it's time to learn something about the NTFS file system.  The Windows operating systems all have a special pool of kernel-mode threads that handle I/O requests asynchronously.  So while the appearance of your code is that one process is doing the file operations synchronously, the reality is that the System.Xml code calls System.IO code which calls Windows API code which calls the IO subsystem asynchronously and waits for completion signals.  You know, the knee bone's connected to the thigh bone and all that.  The important point is that the kernel-mode threads that handle the xmlDoc .Load and .Save operations do not know how to cooperate with each other unless given specific instructions otherwise.

So how do we get the .Load and .Save methods to cooperate?  The answer is to create a FileStream object that both methods can use.  Your new and improved code might look something like this:

//  load the XML document
XmlDocument configFile = new XmlDocument();
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
configFile.Load(fs);
 
// TODO: parse the special comments and modify the in-memory document

// write the document back to the file system
configFile.Save(fs);

And that, friends, should be the end of the story.

Only it's not.  Examine the output, and you'll be tempted to schedule an appointment with your optometrist because you're seeing double.  The config file now has two root nodes; the old section was preserved and a new one got added at the end.  Fortunately, it doesn't take too much effort to fix this little problem.  You need to reset the FileStream object before you write to it, that's all.  Your final code should look something like this:

//  load the XML document
XmlDocument configFile = new XmlDocument();
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
configFile.Load(fs);
 
// TODO: parse the special comments and modify the in-memory document

// write the document back to the file system
fs.Seek(0, SeekOrigin.Begin);
fs.SetLength(0);

configFile.Save(fs);


Pop the cork on the champagne bottle, folks, you've got a solution.  And please do not hesitate to leave a comment if you've got any related insights to share with other readers.

Posted on Monday, March 27, 2006 6:30 PM .NET Gotchas | Back to top


Comments on this post: How To: Modify an Existing Xml File

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Is the 'replaceHere' supposed to be the insertion point for new xml tags? Or does it mean that the whole section, e.g. [PROD] needs to be replaced?
Left by Brian on Mar 28, 2006 11:21 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
It's supposed to be the insertion point; it's hard to imagine a situation in which you would want to replace the entire <system.web> element!

I can see I'll have to post my solution when it's completed. :)
Left by Chris Falter on Mar 28, 2006 11:49 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Chris,
If I got a dollar for every time I recommend people look at the Quickstarts first for answers, I wouldn't need to come to work every day.
Cheers,
Peter
Left by Peter Bromberg on Apr 12, 2006 11:25 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
THANK YOU! THANK YOU! THANK YOU!

I thought I was going nuts! It's stuff like this that really needs to be in the community comments section on MSDN. Unfortunately THIS topic doesn't have anywhere to put in a comment. GRRRRRR

Thanks Again
Brad
Left by Brad Bruce on Apr 14, 2007 1:30 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Hi Chris,

Could you give me an example of how you would modify the in-memory document? Thanks, I'm new at this.
Left by Trung on May 05, 2008 2:58 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
please send it as soon as possible
Left by swapna on May 27, 2008 6:11 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
@Trung and swapna -

Probably the simplest way to modify an in-memory document is to use the XmlDocument class' capabilities.

string theXml; // initialized before we get here
XmlDocument doc = new XmlDocument();
doc.LoadXml(theXml);

// find the element(s) we want to modify, using an XPath query
XmlNode geekNode = doc.SelectSingleNode("/OrgChart/Techies/Geek");

// modify the node
geekNode.InnerText = "Fred";
geekNode.Attributes.RemoveAll();
XmlAttribute attr = doc.CreateAttribute("surname");
attr.Value = "Flintstone";
geekNode.Attributes.Append(attr);

- Chris
Left by Chris Falter on May 28, 2008 9:54 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Hey thanks! I've been searching everywhere trying to figure out WHY I am getting extra tags in my XML after editing a document in Open Xml. Your solution fixed it.

Thanks Again!
Left by jhamlett on Jul 10, 2008 6:48 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
i need to modify values for attributes in the xml and save it back. plz help
Left by prakasam on Jul 29, 2008 4:39 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
@prakasam -

Please see my comment @Trung and swapna above. You'd have to modify it slightly, of course. You will probably end up with something like this:

// load the XML document
string fileName = @"c:\Mydir\Mysubdir\Mydoc.xml";
XmlDocument myDoc = new XmlDocument();
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
myDoc.Load(fs);

// find the element(s) we want to modify, using an XPath query
XmlNode geekNode = doc.SelectSingleNode("/OrgChart/Techies/Geek");

// modify the node
XmlAttribute surNameAttr = geekNode.Attributes["surname"];
surNameAttr.Value = "Flintstone";

// write the document back to the file system
fs.Seek(0, SeekOrigin.Begin);
fs.SetLength(0);
myDoc.Save(fs);

HTH -

Chris

Left by Chris Falter on Jul 30, 2008 6:08 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Hey Guys.. I am trying to use Transformers to save the XML file

but after looking at this I assume I don't need to do anything of that sort..can somebody tell me how Transformers can be used instead of this and which one will be better?
Left by Apoorv on Aug 05, 2008 5:31 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
@apoorv - I assume you are talking about applying an XSL Transform to an XML document. To do so, you would follow the same strategies for opening/loading the XML file and writing it back to the file system as I listed in my 7/30/08 comment. The only change is that you would modify the document by applying an XSL Transform in the area with the comments "find the element(s)" and "modify the node". The .NET XSL engine will use the XSL instructions to find and modify the appropriate nodes/attributes.

- Chris
Left by Chris Falter on Aug 06, 2008 9:49 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
I am still getting the IO exception sometimes, in the following code:

Form_Load()
{
while (true)
{
UpdateXML(@"C:\TrnMgmt.xml");
}

}

private void UpdateXML(string fileName)
{
try
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(fileName);
XmlNode node = xmlDocument.SelectSingleNode("/Transactions/DSTDirty");
node.Attributes.GetNamedItem("Value").Value = "1";
xmlDocument.Save(fileName);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Could you please suggest what could be the reason?

Left by Abnish on Feb 04, 2009 11:09 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Abnish -

You need to use a FileStream to open the doc, then after the modifications you may use the same instance of FileStream to save the new version. You have to reset the FileStream, though. Please see my comment dated 7/30/2008 for sample code.

BTW, I do not understand your while(true) loop in the Form_Load() method. It's an infinite loop, because UpdateXml() returns void, so while(true) will just keep going and going....
Left by Chris Falter on Feb 05, 2009 3:36 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Thanks Chris,

I was facing the problem with the code that I posted on comment dated 2/4/2009. And tried your code also, but It was also failing after repeated iterations of while(true) loop.
I understand that this is infinite loop, I created this for testing purpose.

While further debugging, I figured out that this exception was being raised because Anti-Virus program on my system was keeping a lock on this file.

When I disabled the Anti-Virus program error disappeared.

Thanks for your reply and valuable document.


Left by Abnish on Feb 06, 2009 12:23 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
This is the exact solution I am looking for. But i have a basic question. XmlDocument and FileStream do not compile for me. What packages should I import to use these class objects. Thanks for your help.
Left by shikha on Feb 18, 2009 7:19 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Shikha -

According to the msdn documentation, FileStream is in System.IO and XmlDocument is in System.Xml. Sorry I didn't think to include the assemblies/namespaces for completeness' sake. I don't mind helping, but for this kind of question, you are better off using MSDN than waiting around for answers on a discussion thread. There are a lot of good resources at http://msdn.microsoft.com!
Left by Chris on Feb 18, 2009 3:06 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Thank you thank you thank you for this solution to get around the quirkiness of the XmlDocument and FileStream classes!
Left by Walter Bankovitch on Sep 11, 2009 3:19 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Thanks a lot, I even tried FileMode.Truncate but it didn't work neither.
Left by benjamin on Mar 30, 2010 3:43 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Hi,
Is there any way to display selected portion/element/tag of an existing XML file using xmlDoc.getElementsByTagName or whatever else and let the user modify what is displayed on the screen then let the user save the XML file and close. If so sample code will be most appreciated.

Thanks.
Bobby.
Left by Bobby A on Apr 01, 2010 12:36 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Man, you are a project-saver!
I get angry that things like this are not on MSDN or good examples of XML.
You're the best!
Left by XmlNoob on May 19, 2010 7:38 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Hey Guys.. I am trying to use Transformers to save the XML file

but after looking at this I assume I don't need to do anything of that sort..can somebody tell me how Transformers can be used instead of this and which one will be better?
Left by voiture occasion on Jun 02, 2011 10:59 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Really good post mate.
Left by Simon Earnshaw on Sep 26, 2011 1:39 PM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Hi Chris,
It's really fit with my requirement, But if you share the xml file for which you have given the soluntion code, that would be great for me to understand. As I am beginer for xml stuff.

Thanks
GKumar
Left by GKumar on Nov 15, 2012 12:38 AM

# re: How To: Modify an Existing Xml File
Requesting Gravatar...
Thanks a lot. You made my day :). Was banging my head with this strange (!!!!) exception
Left by Venkat on Mar 10, 2013 10:47 AM

Your comment:
 (will show your gravatar)
 


Copyright © Chris Falter | Powered by: GeeksWithBlogs.net | Join free