JL.
JL.

Reputation: 81262

C# Serialization to file, overwrite if exists

I have the following method (below), as you can see it serializes an object to an XML file. The main problem I am having is I want to get the function to overwrite a file if it exists. I know I could delete the file first if it does exist, but this would also mean that I might induce some error drag into my application. So I want it an all or nothing, overwrite method...

Here is the function, any ideas on how this can be accomplished?

/// <summary>
    /// Serializes an object to an xml file.
    /// </summary>
    /// <param name="obj">
    /// The object to serialize.
    /// </param>
    /// <param name="type">
    /// The class type of the object being passed.
    /// </param>
    /// <param name="fileName">
    /// The filename where the object should be saved to.
    /// </param>
    /// <param name="xsltPath">
    /// Pass a null if not required.
    /// </param>
    public static void SerializeToXmlFile(object obj, Type type, string fileName, string xsltPath )
    {
        var ns = new XmlSerializerNamespaces();
        ns.Add(String.Empty, String.Empty);
        var serializer = new XmlSerializer(type);

        var settings = new XmlWriterSettings {Indent = true, IndentChars = "\t"};


        using (var w = XmlWriter.Create(fileName,settings))
        {

            if (!String.IsNullOrEmpty(xsltPath))
            {
                w.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"" + xsltPath + "\"");

            }
            serializer.Serialize(w, obj, ns);
        }
    }

Upvotes: 2

Views: 11122

Answers (7)

Justin
Justin

Reputation: 1503

Pay attention to the FileMode, if you use FileMode.OpenOrCreate it will not remove the old file contents, if the new contents are smaller then the old contents the xml will be corrupt. Make sure to use FileMode.Create.

here is an extension, it writes to a local .tmp file first, if that succeeds it replaces the desired filename. I chose to use File.Copy to allow .NET.

If you are even more paranoid, you can find examples online of how to create an NTFS transaction for the file swap although it requires extern calls. e.g. https://improve.dk/utilizing-transactional-ntfs-through-dotnet/

depending on where it fails (stage) it will attempt to cleanup the .tmp file.

/// <summary>
/// asynchronous serializing of an object at the path specified by the FileInfo.
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="fi">FileInfo to the target path</param>
/// <param name="ObjectToSerialize">object to serialize</param>
/// <param name="fileShare">File sharing mode during write</param>
public static void SerializeXmlFile<T>(this FileInfo fi, T ObjectToSerialize, FileShare fileShare) //where T:IXmlSerializable
{
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    var TargetFile = fi.FullName;
    var fiTemp = new FileInfo(fi.FullName + ".tmp");

    int Stage = 0;
    try
    {
        try
        {
            using (StreamWriter writer = new StreamWriter(fiTemp.Open(FileMode.Create, FileAccess.Write, fileShare)))
            {
                serializer.Serialize(writer, ObjectToSerialize);
            }
        }
        catch (Exception e)
        {
            throw new IOException("Unable to serialize to temp file, Error: " + e.Message, e);
        }
        Stage = 1;
        try
        {
            fiTemp.CopyTo(TargetFile, true);
            Stage = 2;
        }
        catch (Exception e)
        {
            throw new IOException("Unable to serialize to final file, Error replacing from temp: " + e.Message, e);
        }
        try
        {
            fiTemp.Delete();
        } catch (FileNotFoundException) { }
        catch (Exception e)
        {
            throw new IOException("Unable to cleanup temp file, Error: " + e.Message, e);
        }
        Stage = 3;


        fi.Refresh();
    }
    catch (Exception)
    {
        throw;
    }
    finally
    {
        switch (Stage)
        {
            case 1: // temp is written
            case 2: // temp is copied to destination, not yet deleted
                {
                    try
                    {
                        fiTemp.Delete();
                    }
                    catch (FileNotFoundException) { }

                }
                break;
        }
    }
}

Upvotes: 0

Zack Graber
Zack Graber

Reputation: 1136

The FileStream and XMLWriter should be placed in a using block

using (FileStream fs = File.Create(filename))
using (var w = XmlWriter.Create(fs, settings))
{
    // your code
}

I believe that using the code below will fail to release the file stream. So if you run the code twice in one session it will fail

using (var w = XmlWriter.Create(File.Create(filename), settings))

Upvotes: 2

almathie
almathie

Reputation: 731

You could save your new XML file in a temporary file :

./directory/myNewObject.xml.temp

then rename myNewObject.xml.temp in myNewObject.xml using File.MoveTo

Upvotes: 0

Paul Ruane
Paul Ruane

Reputation: 38580

  1. Open the file using File.Open() with FileMode.Create, FileAccess.Write and FileShare.None.
  2. Pass the stream returned from File.Open() into XmlWriter.Create().

-

FileStream stream = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.None);
using (XmlWriter writer = XmlWriter.Create(stream))
{
    ...
}

Upvotes: 4

particle
particle

Reputation: 3350

You can do something like following. Write your xml to a StringBuilder() and than write content of stringBuilder to file.

    public static void SerializeToXmlFile(object obj, Type type, string fileName, string xsltPath) {
        var ns = new XmlSerializerNamespaces();
        ns.Add(String.Empty, String.Empty);
        var serializer = new XmlSerializer(type);

        var settings = new XmlWriterSettings { Indent = true, IndentChars = "\t" };

        StringBuilder sb = new StringBuilder();
        using (var w = XmlWriter.Create(sb, settings)) {

            if (!String.IsNullOrEmpty(xsltPath)) {
                w.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"" + xsltPath + "\"");

            }
            serializer.Serialize(w, obj, ns);
        }

        File.WriteAllText(fileName, sb.ToString());
    }

Upvotes: 0

Anders Fjeldstad
Anders Fjeldstad

Reputation: 10814

Use the overloaded version of the XmlWriter.Create that takes a Stream instead of a string, and use File.Create to create/overwrite the file:

using (var w = XmlWriter.Create(File.Create(fileName), settings))
...

Upvotes: 14

Summer-Time
Summer-Time

Reputation: 1874

Make a Backup of the Destination file if exist, if an error occurred, write back the file.

Upvotes: 0

Related Questions