XDocument.Save() something blocks file - c#

I intend to load an xml file using XDocument.Load(), update values of some elements, then save it using XDocument.Save(). Reading works fine, saving just won't work.
My code:
class XmlDocHandler
{
private string filePath;
private XDocument xmlDoc;
private IList<XElement> updatedElements;
public IEnumerable<XElement> Elements => xmlDoc.Descendants();
public IEnumerable<XElement> UpdatedElements => updatedElements;
public XmlDocHandler(string filePath)
{
this.filePath = filePath;
ReloadFromFile();
updatedElements = new List<XElement>();
}
public void UpdateElements(IEnumerable<XElement> newElements)
{
updatedElements = new List<XElement>();
foreach (XElement newElement in newElements)
{
XElement element = xmlDoc.Descendants()
.FirstOrDefault(x => x.Name.LocalName == newElement.Name.LocalName);
if (element != null)
{
if (element.Value != newElement.Value)
{
element.Value = newElement.Value;
updatedElements.Add(element);
}
}
}
}
public void ReloadFromFile()
{
bool success = false;
if (File.Exists(filePath))
{
try
{
xmlDoc = XDocument.Load(filePath);
success = true;
}
catch
{
}
}
if (!success)
{
xmlDoc = new XDocument();
}
}
public void WriteToFile()
{
xmlDoc.Save(filePath);
}
}
As far as I can tell, its a serialized set of operations, nothing parallel or other fancy stuff, that could block my file. I've found no indication that XDocument.Load("c:\file.xml") would create a lock on the file.
I've tried to replace the straight forward operations
xmlDoc = XDocument.Load(filePath);
and
xmlDoc.Save(filePath);
with the stream based approaches found here:
XDocument.Save() unable to access file
and here
c# xml.Load() locking file on disk causing errors
so that they look like this:
Loading..
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
xmlDoc = XDocument.Load(fs);
}
or
using (var sr = new StreamReader(filePath))
{
xmlDoc = XDocument.Load(sr);
}
and writing..
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.Write))
{
xmlDoc.Save(fs);
}
No matter what I do, closing the streams properly and making sure the file isn't opened in any editor or otherwise used, the stupid file is always "used by another process".
What exactly am I not seeing here? The file in question resides in my Debug output folder of VS2017 Pro next to the .exe file. I'm not aware that I have limited write access in that folder.

I found the error causing this fiasco!
The reason for this issue has nothing to do with XDocument.Load() or .Save(). While being relieved to have solved my problem, I'm also embarrassed to admit that one of my own implementaions I've made years ago caused this.
I've once made a wrapper class for System.Windows.Forms.OpenFileDialog() that would ease my frequently used configuration of the OFD class. In it I've used something like
var ofd = new OpenFileDialog();
try
{
if((var stream = ofd.OpenFile()) != null)
{
return ofd.FileName;
}
else
{
return "file already opened";
}
}
catch
{
return "error";
}
while keeping the ofd and the stream references as instance variables. Looking at this code now makes me shiver (or laugh) but back then I didn't know any better. Of course this has now bitten me in my a.., because I did not close that stream! A crime I've committed years ago now cost me almost 2 days of work.
My remedy: I've rewritten my OFD wrapper class to use the Microsoft.Win32.OpenFileDialog class, because that does not seem to have any stream based stuff that would lock files unintentionally.
Lesson(s) learned (and what I warmly recommend to others): never trust yourself when using streams or other constructs that can be left open and cause memory leaks. Read up on the using block statement that makes use of the dispose pattern by calling the IDisposable.Dispose() method which usually takes care of such cleanup work. And lastly, use the neat profiling features in VS that weren't available back when I made my OFD wrapper, they help discover such issues.
Thanks to all who helped and to SO to make their help available.

try
fs.Flush();
and maybe
fs.Close();
after writing. AFAIK there is some sort of caching going on in the IO Streams. It might help.
Regards.

Related

Unable to save changes to XML document stored in Sharepoint 2010 Document Library

I am working on a project that requires all SQL connection and query information to be stored in XML files. To make my project configurable, I am trying to create a means to let the user configure his sql connection string information (datasource, catalog, username and password) via a series of text boxes. This input will then be saved to the appropriate node within the SQL document.
I can get the current information from the XML file, and display that information within text boxes for the user's review and correction, but I'm encountering an error when it comes time to save the changes.
Here is the code I'm using to update and save the xml document.
protected void submitBtn_Click(object sender, EventArgs e)
{
SPFile file = methods.web.GetFile("MyXMLFile.xml");
myDoc = new XmlDocument();
byte[] bites = file.OpenBinary();
Stream strm1 = new MemoryStream(bites);
myDoc.Load(strm1);
XmlNode node;
node = myDoc.DocumentElement;
foreach (XmlNode node1 in node.ChildNodes)
{
foreach (XmlNode node2 in node1.ChildNodes)
{
if (node2.Name == "name1")
{
if (node2.InnerText != box1.Text)
{
}
}
if (node2.Name == "name2")
{
if (node2.InnerText != box2.Text)
{
}
}
if (node2.Name == "name3")
{
if (node2.InnerText != box3.Text)
{
node2.InnerText = box3.Text;
}
}
if (node2.Name == "name4")
{
if (node2.InnerText != box4.Text)
{
}
}
}
}
myDoc.Save(strm1);
}
Most of the conditionals are empty at this point because I'm still testing.
The code works great until the last line, as I said. At that point, I get the error "Memory Stream is not expandable." I understand that using a memory stream to update a stored file is incorrect, but I can't figure out the right way to do this.
I've tried to implement the solution given in the similar question at Memory stream is not expandable but that situation is different from mine and so the implementation makes no sense to me. Any clarification would be greatly appreciated.
Using the MemoryStream constructor that takes a byte array as an argument creates a non-resizable instance of a MemoryStream. Since you are making changes to the file (and therefore the underlying bytes), you need a resizable MemoryStream. This can be accomplished by using the parameterless constructor of the MemoryStream class and writing the byte array into the MemoryStream.
Try this:
SPFile file = methods.web.GetFile("MyXMLFile.xml");
myDoc = new XmlDocument();
byte[] bites = file.OpenBinary();
using(MemoryStream strm1 = new MemoryStream()){
strm1.Write(bites, 0, (int)bites.Length);
strm1.Position = 0;
myDoc.Load(strm1);
// all of your edits to the file here
strm1.Position = 0;
// save the file back to disk
using(var fs = new FileStream("FILEPATH",FileMode.Create,FileAccess.ReadWrite)){
myDoc.Save(fs);
}
}
To get the FILEPATH for a Sharepoint file, it'd be something along these lines (I don't have a Sharepoint development environment set up right now):
SPFile file = methods.web.GetFile("MyXMLFile.xml")
var filepath = file.ParentFolder.ServerRelativeUrl + "\\" + file.Name;
Or it might be easier to just use the SaveBinary method of the SPFile class like this:
// same code from above
// all of your edits to the file here
strm1.Position = 0;
// don't use a FileStream, just SaveBinary
file.SaveBinary(strm1);
I didn't test this code, but I've used it in Sharepoint solutions to modify XML (mainly OpenXML) documents in Sharepoint lists. Read this blogpost for more information
You could look into using the XDocument class instead of XmlDocument class.
http://msdn.microsoft.com/en-us/library/system.xml.linq.xdocument.aspx
I prefer it because of the simplicity and it eliminates having to use Memory Stream.
Edit: You can append to the file like this:
XDocument doc = XDocument.Load('filePath');
doc.Root.Add(
new XElement("An Element Name",
new XAttribute("An Attribute", "Some Value"),
new XElement("Nested Element", "Inner Text"))
);
doc.Save(filePath);
Or you can search for an element and update like this:
doc.Root.Elements("The element").First(m =>
m.Attribute("An Attribute").Value == "Some value to match").SetElementValue(
"The element to change", "Value to set element to");
doc.Save('filePath');

How do we use FileMode.Append on C#?

I've been trying to come up with a way for my code to open a file or create one (if my given file name is non-existent). Afterwards, it will run a program that will end up creating an array and I want the contents of that array to be converted into string and appended into the file that I am creating and opening. I've got everything right except for the 'Append' part. It say in the end that the "Object reference not set to an instance of an object." Can you please enlighten me on this one? Help will be much appreciated.
try
{
FileStream fs = new FileStream("inventory.ini", FileMode.OpenOrCreate, FileAccess.Read);
StreamReader reader = new StreamReader(fs);
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
string[] data = line.Split('|');
int code = int.Parse(data[0]);
string name = data[1];
double price = double.Parse(data[2]);
Item item = new Item(code, name, price);
app.array[inventoryCount++] = item;
}
reader.Close();
fs.Close();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
app.Run();
try
{
FileStream fs = new FileStream("inventory.ini", FileMode.Append, FileAccess.Write);
StreamWriter writer = new StreamWriter(fs);
foreach (Item item in app.array)
{
writer.WriteLine(item.Code + "|" + item.Name + "|" + item.Price);
}
writer.Close();
fs.Close();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
You can use another constructor of StreamWriter, that allows appending, and then write like this:
StreamWriter writer = new StreamWriter("inventory.ini", true);
I never used FileStream in my apps, but StreamWriter has been quite reliable. You can also switch to Using statement, then you don't need to Close().
Also I suggest switching to lists, then you will always have the exact amount of items you need inside app.array (which btw needs a better name). So this:
app.array[inventoryCount++] = item;
will change to something like this:
app.list.Add(item);
Aside from memory management headache relief, you no longer need inventoryCount variable, since you can get this value from list.Count;
The general approach here is to minimize amount of code you need to write, for the same amount of functionality. Then you have no place for the error to lurk.
catch (Exception e)
{
Console.WriteLine(e.Message);
}
You are digging yourself a pretty deep hole with exception handling like this. A hard rule in catching an exception is that you restore the state of your program when you handle it. You don't. In particular, you are forgetting to close the file. This then goes wrong, later, when you try to open the file again to write. The exception message is misleading, unfortunately, talking about another process having the file already opened. Not the case, it is your process that still has the file opened.
There are plenty of countermeasures against this failure. You should be using the using statement to ensure the file is closed even if there's an exception. And you'll need to fix your EndOfStream test, it isn't accurate on text files, use a while(true) loop and break when ReadLine() returns null. Which solves the original problem.
But the real fix is to not hide an inconvenient truth. Allowing your program to continue running when a config file is broken just begets more trouble when it doesn't do what you hope it does. And you can't tell because the message you write to the console was scrolled off the screen. Very hard to diagnose.
Remove the try/catch from this code. Now you get to address the real problem.
Note that you can also just use File.AppendText() to open a StreamWriter in append mode.
You should also use using instead of .Close() to close the stream - then it will work even if an exception occurs.
So your code would look more like this:
try
{
using (var writer = File.AppendText("inventory.ini"))
{
foreach (Item item in app.array)
{
if (item != null)
writer.WriteLine(item.Code + "|" + item.Name + "|" + item.Price);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Why don't you use using statement
using (FileStream fs = new FileStream("inventory.ini", FileMode.OpenOrCreate, FileAccess.Read))
using (StreamReader reader = new StreamReader(fs))
{
// do stuff
}

Locking XML file in Web Application

We have a website application which acts as a form designer.
The form data is stored in XML file. Each form has it's own xml file.
When i edit a form, i basically recreate the XML file.
public void Save(Guid form_Id, IEnumerable<FormData> formData)
{
XDocument doc = new XDocument();
XElement formsDataElement = new XElement("FormsData");
doc.Add(formsDataElement);
foreach (FormData data in formData)
{
formsDataElement.Add(new XElement("FormData",
new XAttribute("Id", data.Id)
new XAttribute("Name", data.Name)
// other attributes
));
}
doc.Save(formXMLFilePath);
}
This works good, but i want to make sure that two users won't update at the same time the XML file. I want to lock it somehow.
How can i individually lock the save process for each file?
I could lock the Save function like below, but this will lock all the users, even if they save a different XML file.
private static readonly object _lock = new object();
public void Save(Guid form_Id, IEnumerable<FormData> formData)
{
lock(_lock)
{
XDocument doc = new XDocument();
foreach (FormData data in formData)
{
// Code
}
doc.Save(formXMLFilePath);
}
}
To prevent files being written simultaneously, the XDocument.Save method internally creates a FileStream with the following constructor:
new FileStream(
outputFileName,
FileMode.Create,
FileAccess.Write,
FileShare.Read,
0x1000,
UseAsync)
The option FileShare.Read prevents multiple writes occuring simultaneously. If you attempted to perform two writes simultaneously, you'd wind up with an IOException being thrown.
You might want to write some code to deal with this eventuality. This answer https://stackoverflow.com/a/50800/138578 should provide exactly how to handle a file being locked and re-trying until the file becomes available.
If your documents are small and many, you shouldn't run into this situation very often, if at all.
Try something like this:
FileStream file = new FileStream(formXMLFilePath,FileMode.Create,FileAccress.Write,FileShare.None);
try {
doc.Save(file);
} finally {
file.Close();
}
This uses a FileStream and we supply a FileShare mode. In our case we are exclusively locking the file so that only we can write to it.
However, this simply means that subsequent writes will fail as there is a lock.
To get around this problem I tend to have a queue and a separate thread who's only job is to process the queue and write to the file system.
However another solution is to simply try and access the file, and if that fails wait a little bit then try again. This would let you do something akin to lock where it waits until it can access and then proceeds:
private static bool IsLocked(string fileName)
{
if (!File.Exists(fileName)) return false;
try {
FileStream file = new FileStream(fileName,FileMode.Open,FileAccess.Write,FileShare.None);
try {
return false;
} finally {
file.Close();
}
} catch (IOException) {
return true;
}
}
public void Save(Guid form_Id, IEnumerable<FormData> formData)
{
while (IsLocked(formXMLFilePath)) Thread.Sleep(100);
FileStream file = new FileStream(formXMLFilePath,FileMode.Create,FileAccress.Write,FileShare.None);
try {
doc.Save(file);
} finally {
file.Close();
}
}
You did not closed your xml file connection after opening your xml file. So I think you need to first closed your connection at the end of your function.
you can use this
filename.Close();
I hope this will help you

XML File in use by another process

Recently, I began developing an application that utilizes the use of XML documents in a C# program. Everything worked fine to begin with, but as I edited the code, and added a bit more features to the program in case the XML gets deleted, corrupted, etc, the program didn't want to function properly anymore.
The following code is used when submitting the data and transferring it to XML:
private void doneCreate_Click(object sender, EventArgs e)
{
//Initialize new XMLDocument class.
XmlDocument XmlDoc = new XmlDocument();
//See if the card data file is there, if not, create it.
if (File.Exists(xmlPath) == false)
{
using (FileStream createFile = File.Create(xmlPath))
{
Byte[] FileData = new UTF8Encoding(true).GetBytes(toBase64("<studycards></studycards>"));
// Add some information to the file.
createFile.Write(FileData, 0, FileData.Length);
createFile.Close();
}
XMLData = "<studycards></studycards>";
}
else
{
XMLData = readXML();
if (XMLData == "")
{
XMLData = "<studycards></studycards>";
}
else
{
XMLData = fromBase64(XMLData);
}
}
XmlDoc.LoadXml(XMLData);
XmlElement Group = XmlDoc.CreateElement("Group", null);
XmlAttribute Group_Attr = XmlDoc.CreateAttribute("Name");
Group_Attr.Value = groupName.Text;
Group.Attributes.Append(Group_Attr);
foreach (string[] Card in CardData)
{
try
{
FrontData = Card[0].ToString();
BackData = Card[1].ToString();
NewCard = XmlDoc.CreateElement("Card");
FrontElement = XmlDoc.CreateElement("Front");
FrontElement.InnerText = FrontData;
BackElement = XmlDoc.CreateElement("Back");
BackElement.InnerText = BackData;
NewCard.AppendChild(FrontElement);
NewCard.AppendChild(BackElement);
Group.AppendChild(NewCard);
}
catch
{
break;
}
}
XmlDoc.DocumentElement.AppendChild(Group);
XmlTextWriter write = new XmlTextWriter(xmlPath, null);
write.Formatting = Formatting.Indented;
XmlDoc.Save(write);
}
Upon attempting to click "Done" in the application, I'm presented with the following Exception:
The process cannot access the file 'C:\Users\Toshiba\documents\visual studio 2010\Projects\StudyCards\StudyCards\bin\Debug\Resources\card_data.xml' because it is being used by another process.
Any solutions?
This may well be at least part of the problem:
XmlTextWriter write = new XmlTextWriter(xmlPath, null);
write.Formatting = Formatting.Indented;
XmlDoc.Save(write);
You're never closing the writer, so I would expect it to keep the file open. That will stop future attempts to open the file, until the finalizer kicks in.
Alternatively it could be part of readXML(), which you haven't shown - again, if that leaves the file open, that would cause problems.

Will a using clause close this stream?

I've apparently worked myself into a bad coding habit. Here is an example of the code I've been writing:
using(StreamReader sr = new StreamReader(File.Open("somefile.txt", FileMode.Open)))
{
//read file
}
File.Move("somefile.txt", "somefile.bak"); //can't move, get exception that I the file is open
I thought that because the using clause explicitly called Close() and Dispose() on the StreamReader that the FileStream would be closed as well.
The only way I could fix the problem I was having was by changing the above block to this:
using(FileStream fs = File.Open("somefile.txt", FileMode.Open))
{
using(StreamReader sr = new StreamReader(fs))
{
//read file
}
}
File.Move("somefile.txt", "somefile.bak"); // can move file with no errors
Should closing the StreamReader by disposing in the first block also close the underlying FileStream? Or, was I mistaken?
Edit
I decided to post the actual offending block of code, to see if we can get to the bottom of this. I am just curious now.
I thought I had a problem in the using clause, so I expanded everything out, and it still can't copy, every time. I create the file in this method call, so I don't think anything else has a handle open on the file. I've also verified that the strings returned from the Path.Combine calls are correct.
private static void GenerateFiles(List<Credit> credits)
{
Account i;
string creditFile = Path.Combine(Settings.CreditLocalPath, DateTime.Now.ToString("MMddyy-hhmmss") + ".credits");
StreamWriter creditsFile = new StreamWriter(File.Open(creditFile, FileMode.Create));
creditsFile.WriteLine("code\inc");
foreach (Credit c in credits)
{
if (DataAccessLayer.AccountExists(i))
{
string tpsAuth = DataAccessLayer.GetAuthCode(i.Pin);
creditsFile.WriteLine(String.Format("{0}{1}\t{2:0.00}", i.AuthCode, i.Pin, c.CreditAmount));
}
else
{
c.Error = true;
c.ErrorMessage = "NO ACCOUNT";
}
DataAccessLayer.AddCredit(c);
}
creditsFile.Close();
creditsFile.Dispose();
string dest = Path.Combine(Settings.CreditArchivePath, Path.GetFileName(creditFile));
File.Move(creditFile,dest);
//File.Delete(errorFile);
}
Yes, StreamReader.Dispose closes the underlying stream (for all public ways of creating one). However, there's a nicer alternative:
using (TextReader reader = File.OpenText("file.txt"))
{
}
This has the added benefit that it opens the underlying stream with a hint to Windows that you'll be accessing it sequentially.
Here's a test app which shows the first version working for me. I'm not trying to say that's proof of anything in particular - but I'd love to know how well it works for you.
using System;
using System.IO;
class Program
{
public static void Main(string[] args)
{
for (int i=0; i < 1000; i++)
{
using(StreamReader sr = new StreamReader
(File.Open("somefile.txt", FileMode.Open)))
{
Console.WriteLine(sr.ReadLine());
}
File.Move("somefile.txt", "somefile.bak");
File.Move("somefile.bak", "somefile.txt");
}
}
}
If that works, it suggests that it's something to do with what you do while reading...
And now here's a shortened version of your edited question code - which again works fine for me, even on a network share. Note that I've changed FileMode.Create to FileMode.CreateNew - as otherwise there could still have been an app with a handle on the old file, potentially. Does this work for you?
using System;
using System.IO;
public class Test
{
static void Main()
{
StreamWriter creditsFile = new StreamWriter(File.Open("test.txt",
FileMode.CreateNew));
creditsFile.WriteLine("code\\inc");
creditsFile.Close();
creditsFile.Dispose();
File.Move("test.txt", "test2.txt");
}
}
Note - your using blocks do not need to be nested in their own blocks - they can be sequential, as in:
using(FileStream fs = File.Open("somefile.txt", FileMode.Open))
using(StreamReader sr = new StreamReader(fs))
{
//read file
}
The order of disposal in this case is still the same as the nested blocks (ie, the StreamReader will still dispose before the FileStream in this case).
I would try to use FileInfo.Open() and FileInfo.MoveTo() instead of File.Open() and File.Move(). You could also try to use FileInfo.OpenText(). But these are just suggestions.
Is there any possibility that something else has a lock to somefile.txt?
A simple check from a local (to the file) cmd line
net files
may well give you some clues if anything else has a lock.
Alternatively you can get something like FileMon to take even more details, and check that your app is releasing properly.
Since this doesn't seem to be a coding issue, I'm going to put my syadmin hat on and offer a few suggestions.
Virus scanner on either the client or server that's scanning the file as it's created.
Windows opportunistic locking has a habit of screwing things up on network shares. I recall it being mostly an issue with multiple read/write clients with flat file databases, but caching could certainly explain your problem.
Windows file open cache. I'm not sure if this is still a problem in Win2K or not, but FileMon would tell you.
Edit: If you can catch it in the act from the server machine, then Sysinternal's Handle will tell you what has it open.

Categories

Resources