xmlwriter , it's being used by another process - c#

Here, i created a class, and what i am trying to accomplish is to write the contents from the list into an xml file.
1) At first run, it creates the file and trows an error here: Token EndElement in state EndRootElement would result in an invalid XML document
public static void SaveCellPhoneProducts(List<ProducCellPhone> LocalProducts)
{
XmlWriterSettings localSettings = new XmlWriterSettings();
localSettings.Indent = true;
localSettings.IndentChars = (" ");
//second run, error Occurr here
//xml writer object, CellPhoneProduct
XmlWriter xmlOut = XmlWriter.Create(path, localSettings);
xmlOut.WriteStartDocument();
xmlOut.WriteStartElement("Cell");
foreach(ProducCellPhone localProduct in LocalProducts)
{
WriteCellPhoneProductBase(localProduct, xmlOut);
}
//first Run error is thrown in here.
xmlOut.WriteEndElement();
xmlOut.Close();
}
2) When i rerun on the second time, the error is in same method.
public static void SaveCellPhoneProducts(List<ProducCellPhone> LocalProducts)
{
XmlWriterSettings localSettings = new XmlWriterSettings();
localSettings.Indent = true;
localSettings.IndentChars = (" ");
//xml writer object, CellPhoneProduct
XmlWriter xmlOut = XmlWriter.Create(path, localSettings);
xmlOut.WriteStartDocument();
xmlOut.WriteStartElement("Cell");
foreach(ProducCellPhone localProduct in LocalProducts)
{
WriteCellPhoneProductBase(localProduct, xmlOut);
}
xmlOut.WriteEndElement();
xmlOut.Close();
}
The whole class i here:
class ProductCellPhoneDB
{
private const string path = #"..\..\CellPhoneProducts.xml";
public static List<ProducCellPhone> GetCellPhoneProducts()
{
List<ProducCellPhone> localCellPhoneProducts =
new List<ProducCellPhone>();
XmlReaderSettings localSettings = new XmlReaderSettings();
localSettings.IgnoreWhitespace = true;
localSettings.IgnoreComments = true;
XmlReader xmlIn = (XmlReader.Create(path,localSettings));
if (xmlIn.ReadToDescendant("Cell"))
{
do
{
ProducCellPhone localProduct = null;
xmlIn.ReadStartElement("Cell");
localCellPhoneProducts.Add(localProduct);
}
while (xmlIn.ReadToNextSibling("Cell"));
}
xmlIn.Close();
return localCellPhoneProducts;
}
public static void SaveCellPhoneProducts(List<ProducCellPhone> LocalProducts)
{
XmlWriterSettings localSettings = new XmlWriterSettings();
localSettings.Indent = true;
localSettings.IndentChars = (" ");
//Error Occurr here
//xml writer object, CellPhoneProduct, error is being used by other process?
XmlWriter xmlOut = (XmlWriter.Create(path, localSettings));
xmlOut.WriteStartDocument();
xmlOut.WriteStartElement("Cell");
foreach(ProducCellPhone localProduct in LocalProducts)
{
WriteCellPhoneProductBase(localProduct, xmlOut);
}
//ERROR Token EndElement in state EndRootElement would result in an invalid XML document
xmlOut.WriteEndElement();
xmlOut.Close();
}
private static void ReadCellphoneProductBase(XmlReader xmlIn, ProducCellPhone localProduct)
{
localProduct.Iemi = xmlIn.ReadElementContentAsString();
localProduct.Model = xmlIn.ReadContentAsString();
localProduct.Price = xmlIn.ReadContentAsDecimal();
}
private static void WriteCellPhoneProductBase(ProducCellPhone localProduct,
XmlWriter xmlout)
{
xmlout.WriteElementString("IEMI", localProduct.Iemi);
xmlout.WriteElementString("Model", localProduct.Model);
xmlout.WriteElementString("Price", Convert.ToString(localProduct.Price));
xmlout.WriteEndElement();
}
}
Any suggestions would be helpful. Thanks community. !

The first error
you get is likely because the WriteStartElement and WriteEndElement calls are not matched. You do xmlOut.WriteStartElement("Cell"); once, but do xmlout.WriteEndElement(); several times, once for each ProducCellPhone in LocalProducts, plus another time after the foreach.
To solve this (if I guessed your XML document structure right), you should change your WriteCellPhoneProductBase method to:
private static void WriteCellPhoneProductBase(ProducCellPhone localProduct,
XmlWriter xmlout)
{
xmlOut.WriteStartElement("Cell");
xmlout.WriteElementString("IEMI", localProduct.Iemi);
xmlout.WriteElementString("Model", localProduct.Model);
xmlout.WriteElementString("Price", Convert.ToString(localProduct.Price));
xmlout.WriteEndElement();
}
And remove the WriteStartElement and WriteEndElement lines from SaveCellPhoneProducts (see below).
The second error is probably because the XmlWriter used when you got the first error was not disposed and has not closed the file handle. You should always use a using block to ensure IDisposable resources get disposed, also when an exception occurs. You should change your method to:
public static void SaveCellPhoneProducts(List<ProducCellPhone> LocalProducts)
{
//xml writer settings
XmlWriterSettings localSettings = new XmlWriterSettings();
localSettings.Indent = true;
localSettings.IndentChars = (" ");
using (var xmlOut = XmlWriter.Create(path, localSettings))
{
xmlOut.WriteStartDocument();
//write each product on xml file
foreach(ProducCellPhone localProduct in LocalProducts)
WriteCellPhoneProductBase(localProduct, xmlOut);
xmlOut.WriteEndDocument()
}
}
For your GetCellPhoneProducts follow the same using block approach.

Related

XMLWriter cannot access file that it just created

I have a service that is processing data and exporting out xml files for another service, which usually handles small amount of data(About 5 files a minute) but sometimes a large chunk of data is dumped and it may handle needing to create 100 files all at once.
The service runs mostly fine but it occasionally runs into an error:
The process cannot access the file 'C:\File.xml' because it is being used by another process.
This is strange because the file should be created, written, and closed all in one shot, so I don't know what could be giving this problem. The issue seems to happen more frequently under heavy load, but still occurs regularly under light loads as well. Naturally this never shows up while debugging or I probably would have fixed this by now.
Here is the function that actually writes the file:
public bool WriteDocument(Object Data)
{
XmlDocument X = new XmlDocument();
XmlSchemaSet schemaSet = new XmlSchemaSet();
schemaSet.Add(null, "Schema.xsd");
X.Schemas = schemaSet;
if (!Directory.Exists("OutputDirectory"))
{
Directory.CreateDirectory("OutputDirectory");
}
string File = Path.Combine("OutputDirectory", #"File" + ".xml");
XmlSerializer Encoder = new XmlSerializer(typeof(Object));
//Do stuff to data object to prepare for output
using (MemoryStream buffer = new MemoryStream())
{
Encoder.Serialize(buffer, Data);
buffer.Position = 0;
X.Load(buffer);
X.Validate((sender, e) =>
{
if (e.Severity == XmlSeverityType.Error)
{
result = false;
}
else
{
result = true;
}
});
}
if (result)
{
while (System.IO.File.Exists(File))
{
File = MakeUniqueName(File);
}
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = Encoding.UTF8;
using (XmlWriter Writer = XmlWriter.Create(File, settings))
{
X.WriteTo(Writer);
Writer.Close();
}
}
return result;
}

C# append object to xml file using serialization

I am trying append a serialized object to an existing xml file beneath the root element, which I thought would be simple but is proving to be a little challenging.
The problem is in the AddShortcut method but I added some more code for completeness.
I believe what I need to do is:
load the file into an XmlDocument.
navigate to the node I want to append beneath (here the node name is Shortcuts).
create some type of writer and then serialize the object.
save the XmlDocument.
The trouble is in steps 2 and 3. I have tried different variations but I think using XPathNavigator somehow to find the "root" node to append under is a step in the right direction.
I have also looked at almost every question on Stack Overflow on the subject.
Any suggestions welcome. Here is my code
class XmlEngine
{
public string FullPath { get; set; } // the full path to the xmlDocument
private readonly XmlDocument xDoc;
public XmlEngine(string fullPath, string startElement, string[] rElements)
{
FullPath = fullPath;
xDoc = new XmlDocument();
CreateXmlFile(FullPath, startElement, rElements);
}
public void CreateXmlFile(string path, string startElement, string[] rElements)
{
try
{
if (!File.Exists(path))
{
// create a txt writer
XmlTextWriter wtr = new XmlTextWriter(path, System.Text.Encoding.UTF8);
// make sure the file is well formatted
wtr.Formatting = Formatting.Indented;
wtr.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
wtr.WriteStartElement(startElement);
wtr.Close();
// write the top level root elements
writeRootElements(path, rElements);
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
Console.WriteLine("Could not create file: " + path);
}
}
public void AddShortcut(Shortcut s)
{
xDoc.Load(FullPath);
rootNode = xDoc.AppendChild(xDoc.CreateElement("Shortcuts"));
var serializer = new XmlSerializer(s.GetType());
using (var writer = new StreamWriter(FullPath, true))
{
XmlWriterSettings ws = new XmlWriterSettings();
ws.OmitXmlDeclaration = true;
serializer.Serialize(writer, s);
}
xDoc.Save(FullPath);
}
}
This code sample worked for me:
xml:
<?xml version="1.0" encoding="UTF-8"?>
<Launchpad>
<Shortcuts>
<Shortcut Id="1">
<Type>Folder</Type>
<FullPath>C:\SomePath</FullPath>
<Name>SomeFolderName</Name>
</Shortcut>
</Shortcuts>
</Launchpad>
Method:
public void AddShortcut(Shortcut s)
{
xDoc.Load(FullPath);
var rootNode = xDoc.GetElementsByTagName("Shortcuts")[0];
var nav = rootNode.CreateNavigator();
var emptyNamepsaces = new XmlSerializerNamespaces(new[] {
XmlQualifiedName.Empty
});
using (var writer = nav.AppendChild())
{
var serializer = new XmlSerializer(s.GetType());
writer.WriteWhitespace("");
serializer.Serialize(writer, s, emptyNamepsaces);
writer.Close();
}
xDoc.Save(FullPath);
}
load the file into an XmlDocument.
navigate to the node I want to append beneath (here the node name is Shortcuts).
create some type of writer and then serialize the object.
save the XmlDocument
So:
public void AddShortcut(Shortcut s)
{
// 1. load existing xml
xDoc.Load(FullPath);
// 2. create an XML node from object
XmlElement node = SerializeToXmlElement(s);
// 3. append that node to Shortcuts node under XML root
var shortcutsNode = xDoc.CreateElement("Shortcuts")
shortcutsNode.AppendChild(node);
xDoc.DocumentElement.AppendChild(shortcutsNode);
// 4. save changes
xDoc.Save(FullPath);
}
public static XmlElement SerializeToXmlElement(object o)
{
XmlDocument doc = new XmlDocument();
using(XmlWriter writer = doc.CreateNavigator().AppendChild())
{
new XmlSerializer(o.GetType()).Serialize(writer, o);
}
return doc.DocumentElement;
}
This post

How to use OmitXmlDeclaration and QuoteChar in c# when saving xml

i have some XML files and i have wrote a c# application to check missing elements, nodes and save it back. In my XMLs attributes are use single quotes (ex: <Person name='Nisala' age='25' >). But when saving C# application convert those quotes to double quotes. Then i found following code to save using single quotes
using (XmlTextWriter tw = new XmlTextWriter(file, null))
{
tw.Formatting = Formatting.Indented;
tw.Indentation = 3;
tw.IndentChar = ' ';
tw.QuoteChar = '\'';
xmlDoc.Save(tw);
}
}
but it will append XML declaration there. then i found this code to remove xml declaration
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = true;
xws.ConformanceLevel = ConformanceLevel.Fragment;using (XmlWriter xw = XmlWriter.Create(file, xws)){
xmlDoc.Save(xw);
}
then again XML declaration is appending to text. How can i use both of them?
i have tried following code too, but no use of it
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = true;
xws.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlTextWriter tw = new XmlTextWriter(file, null))
{
tw.Formatting = Formatting.Indented;
tw.Indentation = 3;
tw.IndentChar = ' ';
tw.QuoteChar = '\'';
using (XmlWriter xw = XmlWriter.Create(tw, xws))
{
xmlDoc.Save(xw);
}
}
The XML declaration is written by calling WriteStartDocument on the XmlWriter implementation. The behaviour of this is can be altered when you use the recommended XmlWriter.Create with XmlWriterSettings.
However, the recommended method doesn't allow you to change the quote character.
The only solution I can think of is to create your own writer, deriving from XmlTextWriter. You would then override WriteStartDocument to prevent any declaration being written:
public class XmlTextWriterWithoutDeclaration : XmlTextWriter
{
public XmlTextWriterWithoutDeclaration(Stream w, Encoding encoding)
: base(w, encoding)
{
}
public XmlTextWriterWithoutDeclaration(string filename, Encoding encoding)
: base(filename, encoding)
{
}
public XmlTextWriterWithoutDeclaration(TextWriter w)
: base(w)
{
}
public override void WriteStartDocument()
{
}
}
And use as you are now:
using (var tw = new XmlTextWriterWithoutDeclaration(file, null))
{
tw.Formatting = Formatting.Indented;
tw.Indentation = 3;
tw.IndentChar = ' ';
tw.QuoteChar = '\'';
xmlDoc.Save(tw);
}

XmlDoc throws the process cannot access the file from tome to time?

I have the following code for writing XML file based on datacontracts
public static void LogDataContractToFile(string XMLStringToLog, string filePathAndName)
{
//String documentPath = string.Empty;
String xmlObject = string.Empty;
FileInfo fileinfo;
XmlDocumentFragment xmlDocumentFragment;
XmlTextWriter xmlWriter;
XmlDocument xmlDocument = null;
lock (LogDataContractToFileLock)
{
filePathAndName = filePathAndName.ToLower();
while (_workingWithFile.Contains(filePathAndName))
Thread.Sleep(1000);
_workingWithFile.Add(filePathAndName.ToLower());
try
{
#region Create XMLFile
fileinfo = new FileInfo(filePathAndName);
if (!fileinfo.Exists)
{
DirectoryInfo info = new DirectoryInfo(fileinfo.DirectoryName);
if (info.Exists == false)
info.Create();
using (xmlWriter = new XmlTextWriter(filePathAndName, System.Text.Encoding.UTF8))
{
xmlWriter.Formatting = Formatting.Indented;
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("root");
xmlWriter.WriteStartElement("objects");
xmlWriter.WriteEndElement();
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
xmlWriter.Close();
}
}
else
{
//Se så att filen är 50 MB eller mindre
while (fileinfo.Length > 52428800)
{
xmlDocument = new XmlDocument();
xmlDocument.Load(filePathAndName);
xmlDocument.RemoveChild(xmlDocument.LastChild);
xmlDocument.Save(filePathAndName);
xmlDocument = null;
}
}
#endregion
xmlObject = XMLStringToLog;
//Open document
xmlDocument = new XmlDocument();
xmlDocument.Load(filePathAndName);
//Create a new fragment in current document
xmlDocumentFragment = xmlDocument.CreateDocumentFragment();
xmlDocumentFragment.InnerXml = xmlObject;
//Add new fragment after the first child
xmlDocument.DocumentElement.InsertBefore(xmlDocumentFragment, xmlDocument.DocumentElement.FirstChild);
xmlDocument.Save(filePathAndName);
xmlDocument = null;
}
finally
{
_workingWithFile.Remove(filePathAndName.ToLower());
}
}
}
The problem is that from time to time I get a The process cannot access the file exception? XmlDocument do not have any dispose so I can´t use using. How should this be handled properly?
Note that Im stuck on .NET 4.0.
To safely check for an element and add it if it doesn't exist you should use a ConcurrentDictionary:
private readonly ConcurrentDictionary<string,bool> _workingWithFile = new ConcurrentDictionary<string,bool>();
public static void LogDataContractToFile(string XMLStringToLog, string filePathAndName)
{
...
lock (LogDataContractToFileLock)
{
...
while(!_workingWithFile.TryAdd(filePathAndName, true))
{
Thread.Sleep(1000);
}
...
try
{
...
}
finally
{
//Perhaps check the result here.
bool result;
_workingWithFile.TryRemove(filePathAndName, out result);
}
}
}

C#: XmlTextWriter.WriteElementString fails on empty strings?

I'm using XmlTextWriter and its WriteElementString method, for example:
XmlTextWriter writer = new XmlTextWriter("filename.xml", null);
writer.WriteStartElement("User");
writer.WriteElementString("Username", inputUserName);
writer.WriteElementString("Email", inputEmail);
writer.WriteEndElement();
writer.Close();
The expected XML output is:
<User>
<Username>value</Username>
<Email>value</Email>
</User>
However, if for example inputEmail is empty, the result XML I get as as follows:
<User>
<Username>value</Username>
<Email/>
</User>
Whereas I would expect it to be:
<User>
<Username>value</Username>
<Email></Email>
</User>
What am I doing wrong? Is there a way to achieve my expected result in a simple way using XmlTextWriter?
Your output is correct. An element with no content should be written as <tag/>.
You can force the use of the full tag by calling WriteFullEndElement()
writer.WriteStartElement("Email");
writer.WriteString(inputEmail);
writer.WriteFullEndElement();
That will output <Email></Email> when inputEmail is empty.
If you want to do that more than once, you could create an extension method:
public static void WriteFullElementString(this XmlTextWriter writer,
string localName,
string value)
{
writer.WriteStartElement(localName);
writer.WriteString(value);
writer.WriteFullEndElement();
}
Then your code would become:
writer.WriteStartElement("User");
writer.WriteFullElementString("Username", inputUserName);
writer.WriteFullElementString("Email", inputEmail);
writer.WriteEndElement();
It doesn't fail <Tag/> is just a shortcut for <Tag></Tag>
Your code should be:
using (XmlWriter writer = XmlWriter.Create("filename.xml"))
{
writer.WriteStartElement("User");
writer.WriteElementString("Username", inputUserName);
writer.WriteElementString("Email", inputEmail);
writer.WriteEndElement();
}
This avoids resource leaks in case of exceptions, and uses the proper way to create an XmlReader (since .NET 2.0).
Leaving this here in case someone needs it; since none of the answers above solved it for me, or seemed like overkill.
FileStream fs = new FileStream("file.xml", FileMode.Create);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter w = XmlWriter.Create(fs, settings);
w.WriteStartDocument();
w.WriteStartElement("tag1");
w.WriteStartElement("tag2");
w.WriteAttributeString("attr1", "val1");
w.WriteAttributeString("attr2", "val2");
w.WriteFullEndElement();
w.WriteEndElement();
w.WriteEndDocument();
w.Flush();
fs.Close();
The trick was to set the XmlWriterSettings.Indent = true and add it to the XmlWriter.
Edit:
Alternatively you can also use
w.Formatting = Formatting.Indented;
instead of adding an XmlWriterSettings.
Tried solving this with another approach, might need optimization.
public class SerializeConfig<T> where T : class
{
public static string Serialize(T type)
{
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Indent = true,
OmitXmlDeclaration = true
};
var sb = new StringBuilder();
var serializer = new XmlSerializer(type.GetType());
using (var writer = XmlWriter.Create(sb, settings))
{
serializer.Serialize(writer, type);
}
return sb.ToString().FixXmlClosingTags();
}
}
internal static class InsertStringExtention
{
public static string FixXmlClosingTags(this string xmlString)
{
var sb = new StringBuilder();
var xmlTags = xmlString.Split('\r');
foreach (var tag in xmlTags)
{
if (tag.Contains("/>"))
{
var tagValue = tag.Replace("<", "").Replace("/>", "").Trim();
var firstPart = tag.Substring(0, tag.IndexOf('<'));
var newTag = $"{firstPart}<{tagValue}></{tagValue}>";
sb.Append(newTag);
}
else
{
sb.Append(tag);
}
}
return sb.ToString();
}
}

Categories