3

My C# is a bit rusty and I've never written XML with it before. I'm having trouble getting the XML to write to a file if I attempt to write anything other than elements. Here is the test code that I have:

var guiPath = txtGuiPath.Text;
MessageBox.Show("Dumping File: " + guiPath);

try
{
    var writer = new XmlTextWriter("client_settings.xml", null);
    writer.WriteStartDocument();
    writer.WriteComment("Config generated on 01/01/01");
    writer.WriteStartElement("Config");
    writer.WriteStartElement("GuiPath");
    writer.WriteString(guiPath);
    writer.WriteEndElement();
    writer.WriteEndElement();
    writer.WriteEndDocument();
    writer.Close();
} catch (Exception ex) {
    MessageBox.Show(ex.Message);
}
MessageBox.Show("Finished Dumping");

If guiPath is blank I get the following XML:

<?xml version="1.0"?>
<!--Config generated on 01/01/01-->
<Config>
    <GuiPath />
</Config>

but if there is any text inside guiPath then nothing gets written to the file. I can even delete the client_settings.xml file and fire this code off over and over and the XML file never gets generated unless guiPath is empty. Passing something like "This is a test" to WriteString() works as well.

Update

Since I'm trying to write out a system path, that seems to be the problem. If I strip out all the backslashes it will write the resulting string correctly, but if I pass it to WriteString or WriteCData the XML will not write at all.

Update 2

Turns out that the reason I was having so many problems is because the XML file was being generated in whatever path guiPath was set to, not into the directory that the app was running from (so to me it looked like it wasn't being generated at all). So, if I had guiPath set to 'C:\Program Files\externalApp\appName.exe', it was saving the XML file as 'C:\ProgramFiles\externalApp\client_settings.xml' instead of in the startup folder for the app. Why, I don't know. I started passing Application.StartupPath and appended the filename to that and it works great now.

Thanks for all the help!

dragonmantank
  • 15,243
  • 20
  • 84
  • 92

7 Answers7

12

You might want to examine the API in System.Xml.Linq. It's a bit of a more flexible approach to generating and writing XML. Writing your document might go roughly like this:

XDocument document = new XDocument();
document.Add(new XComment("Config generated on 01/01/01"));
document.Add(new XElement("Config", new XElement("GuiPath", guiPath)));

// var xmlWriter = new XmlTextWriter("client_settings.xml", null);
// document.WriteTo(xmlWriter);

// thanks to Barry Kelly for pointing out XDocument.Save()
document.Save("client_settings.xml");
mqp
  • 70,359
  • 14
  • 95
  • 123
3

Why not create a simple class to hold all the data you need and then serialize it using XmlSerializer, rather than manually generating it line by line? You can even use the attributes in System.Xml.Serialization to control the output if you need:

using System;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;

namespace Foo
{    
    [XmlRoot(ElementName = "Config")]
    public class Config
    {        
        public String GuiPath { get; set; }

        public Boolean Save(String path)
        {
            using (var fileStream = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
            {
                try
                {
                    var serializer = new XmlSerializer(typeof(Config));
                    serializer.Serialize(fileStream, this);
                    return true;
                }
                catch(Exception ex)
                {
                    MessageBox.Show(ex.Message);
                    // Other exception handling here
                    return false;
                }
            }
        }

        public static Config Load(String path)
        {
            using (var fileStream = File.Open(path, FileMode.Open, FileAccess.Read))
            {
                try
                {
                    var serializer = new XmlSerializer(typeof(Config));
                    return (Config)serializer.Deserialize(fileStream);
                }
                catch(Exception ex)
                {
                    MessageBox.Show(ex.Message);
                    // Other exception handling here
                    return null;
                }
            }
        }
    }
}

This way you don't have to worry about manually encoding strings if they have odd characters - the serializer will do that for you.

This also has the added benefit of being able to be serialized back into the class so you can have strongly typed access to the structure, if you ever need to do that.

Daniel Schaffer
  • 56,753
  • 31
  • 116
  • 165
  • 2
    Two things. Firstly: "catch { return false; }" - JUST DON'T. DO NOT BLINDLY SWALLOW EXCEPTIONS. Secondly, read about the 'using' construct. It is the correct idiom in C# for the File.Open / try / finally { Close() } pattern. – Barry Kelly Jan 28 '09 at 20:47
  • I figured that it was implicit that he'd add whatever error handling he needed, apparently not... – Daniel Schaffer Jan 28 '09 at 21:00
  • Daniel, you're still catching the exception inside the Save and Load methods. Returning 'null' isn't the correct way to signal an error to calling code; the correct way is to let the exception flow out, and let the caller deal with handling it. – Barry Kelly Jan 30 '09 at 02:49
  • Barry, the code is just an *example* - if he wanted to use it, which judging by the accepted answer, he doesn't, he'd handle the exceptions however he wanted. And for what it's worth, the "correct" way depends completely on the domain, which in this case hasn't been explicitly presented to us. – Daniel Schaffer Jan 30 '09 at 04:18
1

Hmm, seems likely that the "real" guiPath contains characters that are breaking XML validation and the XmlTextWriter with it.

May I suggest you try .WriteCData() (instead of .WriteString() that is)

annakata
  • 74,572
  • 17
  • 113
  • 180
1

What do you want the output to be? If you were looking for something like:

<?xml version="1.0"?>
<!--Config generated on 01/01/01-->
<Config>
    GuiPath="c:\some\path\here\"
</Config>

Then you need to change your WriteString to:

writer.WriteAttributeString("GuiPath", guiPath);

Or, if you wanted:

<GuiPath>c:\some\path\here\</GuiPath>

Then you need to write

writer.WriteElementString("GuiPath", guiPath);
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
1

Nobody else has mentioned it, but I think I had better: strongly consider using the using statement when working with IDisposable implementations such as XmlTextWriter etc.

This is important not just for closing resources, such as the underlying stream or text writer, but also to make sure any buffers have been flushed, and to make sure any remaining unclosed elements are closed.

So, when you see mquander's anwser, consider this instead:

using (var xmlWriter = new XmlTextWriter("client_settings.xml", null))
{
    // ...
}

Similarly, in Daniel's answer, don't blindly swallow exceptions, and strongly consider using the using statement on the return value of File.Open (which probably ought to be File.OpenText to be idiomatic, but there are many other shortcomings in style with Daniel's answer at the time of writing).

Community
  • 1
  • 1
Barry Kelly
  • 41,404
  • 5
  • 117
  • 189
0

I would use the System.XML.Linq.XElement class

Note sure about the comment but the Config part would go something like this.

XElement root = new XElement("Config");
root.Add(new XElement("GuiPath", guiPath);
root.Save("client_settings.xml");

Edit: mquander's example is better. Look at that.

Ray
  • 45,695
  • 27
  • 126
  • 169
-1

You need to escapify the contents before writing them out, to make sure that they're valid strings. I don't know of a .NET routine to do it automatically, unfortunately -- the question has been asked here before.

Coderer
  • 25,844
  • 28
  • 99
  • 154