tags:

views:

1068

answers:

9

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!

+2  A: 

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
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
I figured that it was implicit that he'd add whatever error handling he needed, apparently not...
Daniel Schaffer
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
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
A: 

I'd strongly recommend using LINQ to XML if you're using .NET 3.5, but I'm surprised that you're getting nothing in the file at the moment.

Does the first message box show, but not the exception one? What happens if you debug through it line by line?

Jon Skeet
I get the first 'Dumping' message box and the last 'Finished' message box, no exception is thrown so I don't get the one in the catch statement
dragonmantank
And there's absolutely nothing in the file? What if you call writer.Flush() at various points?
Jon Skeet
+2  A: 

Put a break point in and verify what's actually being returned from the .Text property. I see nothing wrong with this code.

David Morton
Yup, I actually have that in the very first message box. If there is something in txtGuiPath.Text it appears correctly in the message box.
dragonmantank
Just because it appears correctly in the message box does not mean it is valid XML, many valid messagebox characters are invalid XML characters.
Dour High Arch
That should be taken care of by WriteString though.
Jon Skeet
+1  A: 

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
WriteCData() seems to have the same problem. If I strip out all of the backslashes then it will write to the XML file correctly.
dragonmantank
now *that* is seriously wierd - cdata should silver bullet any character problem
annakata
+9  A: 

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");
mquander
Use 'using' statement on that XmlTextWriter you create. But however, do consider that XDocument has a Save method, which does the work for you.
Barry Kelly
Good point, I forgot about that. I made my example code better!
mquander
Also, those nice LINQ features are only in .NET 3.5 and up, right?
marc_s
I've been meaning to look into LINQ anyway, I didn't know that it worked with XML as well as SQL. Thanks!
dragonmantank
A: 

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
A: 

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
Wouldn't WriteAttributeString give you <Config GuiPath="C:\my\path" /> ?
Ray
No. The XmlWriter doesn't seem to work that way. Doing WriteStartElement followed immediately by WriteEndElement results in <Config></Config>.
Jim Mischel
+1  A: 

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).

Barry Kelly
Fixed my answer, would appreciate you removing the -1, if that was indeed you.
Daniel Schaffer
A: 

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