XML File in use by another process - c#

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.

Related

XDocument.Save() something blocks file

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.

How to read and write from file in C# UWA (Universal Windows App)

I am trying to figure out how to read and write files in a UWA application. I understand that I need to open a FileStreamm, but I can't figure out how to do that.
I started with this code:
FileStream fs = new FileStream(#"C:\XML\test.txt", FileMode.Create, FileAccess.Write);
seems to work, no red lines.
At the end of all of that I am told to put in Flush and Close, like this:
FileStream fs = new FileStream(#"C:\XML\test.txt", FileMode.Create,
...
fs.Flush();
fs.Close();
Now, this is where I hit a snag, because fs.Close(); is not even on the list of functions on fs. I just get a red line in my IDE if I try to hardcode it.
Can someone please take the time to help me understand how to do this with UWA? For some reason it seems like there is a different approach in Windows 10 apps, and I have a VERY hard time finding anything that shows me how to do it right. All the tutorials and SOF forum input are about older versions (non-UWA).
When I do this in a console application it all works as expected.
My end goal is to be able to read and write to an XML file in this kind of fashion:
XDocument doc = XDocument.Load(input);
XElement person = doc.Element("Person");
person.Add(new XElement("Employee",
new XElement("Name", "David"),
new XElement("Dept", "Chef")));
doc.Save(output);
I'm going down this path because an answer to my previous question told me to use a FileStream, but I simply cannot make that work in UWA.
You cannot just access any file from a Universal Windows App. Access to the file system is restricted.
See the documentation for details.
To help you further we need to know more about your application. What kind of files do you want to access for what reason?
Example on how to read an Xml File, modify it and store it in an Universal app. You need a button with the following Click handler and a TextBox named "TextBoxLog".
private async void ButtonDemo_Click(object sender, RoutedEventArgs e)
{
// Get our local storage folder
var localFolder = ApplicationData.Current.LocalFolder;
XmlDocument xmlDocument;
// Try to get file
var file = await localFolder.TryGetItemAsync("MyData.xml") as IStorageFile;
if(file != null)
{
// File exists -> Load into XML document
xmlDocument = await XmlDocument.LoadFromFileAsync(file);
}
else
{
// File does not exist, create new document in memory
xmlDocument = new XmlDocument();
xmlDocument.LoadXml(#"<?xml version=""1.0"" encoding=""UTF-8""?>" + Environment.NewLine + "<root></root>");
}
// Now show the current contents
TextBoxLog.Text = "";
var lEntries = xmlDocument.GetElementsByTagName("Entry");
foreach(var lEntry in lEntries)
{
TextBoxLog.Text += lEntry.InnerText + Environment.NewLine;
}
// Now add a new entry
var node = xmlDocument.CreateElement("Entry");
node.InnerText = DateTime.Now.ToString();
xmlDocument.DocumentElement.AppendChild(node);
// If the file does not exist yet, create it
if(file == null)
{
file = await localFolder.CreateFileAsync("MyData.xml");
}
// Now save the document
await xmlDocument.SaveToFileAsync(file);
}
Okay, the (simple) solution is to put the xml-file in the PROJECTFOLDER/bin/x86/debug/appX and then write the data to a list this way:
public class dataRaw
{
public string data { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
}
//You can call this class with x = collectionGenerator.getList() (it returns a list<T>)
public class collectionGenerator
{
public static List<dataRaw> getList()
{
//This is the xml file in the folder
var doc = XDocument.Load("Data.xml");
//This parse the XML and adds in to the list "dataList"
var dataList = doc.Root
.Descendants("Person")
.Select(node => new dataRaw
{
//data, firstName and lastName are in app variables from dataRaw put into listData.
//Number, FirstName and LastName are the nodes in the XML file.
data = node.Element("Number").Value,
firstName = node.Element("FirstName").Value,
lastName = node.Element("LastName").Value,
})
.ToList();
return dataList;
}
}

How to resolve IOException "file used by another process" while saving XmlDocument?

When I'm trying to save the XML Document I edited the IOException "file used by another process" occured when I try to save that document.
Any ideas how to solve this?
Note: This method is called everytime a new element in the XmlDocument should be written.
public void saveRectangleAsXMLFragment()
{
XmlDocument doc = new XmlDocument();
doc.Load("test.xml");
XmlDocumentFragment xmlDocFrag = doc.CreateDocumentFragment();
String input = generateXMLInput();
xmlDocFrag.InnerXml = input;
XmlElement mapElement = doc.DocumentElement;
mapElement.AppendChild(xmlDocFrag);
input = null;
mapElement = null;
xmlDocFrag = null;
doc.Save("test.xml");
}
Its probably one of your other methods, or other part of the code which opened the file and didnt calose it well. Try to search for this kind of problem.
try this if your's application is only access that .xml file
1. Create a Object globally
object lockData = new object();
2.Use than object to lock statement where you save and load xml
lock(lockData )
{
doc.Load("test.xml");
}
lock(lockData )
{
doc.Save("test.xml");
}
From Jon Skeet's related answer (see https://stackoverflow.com/a/8354736/4151626)
There seems to be a bug in XmlDocument.Save()'s treatment of the file stream, where it becomes pinned and is neither Closed() nor Disposed(). By taking direct control of the creation and disposition of the stream outside of the XmlDocument.Save() I was able to get around this halting error.
//e.g.
XmlWriter xw = new XmlWriter.Create("test.xml");
doc.Save(xw);
xw.Close();
xw.Dispose();

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 can I put a content in a mergefield in docx

I'm developing a web application with asp.net and I have a file called Template.docx that works like a template to generate other reports. Inside this Template.docx I have some MergeFields (Title, CustomerName, Content, Footer, etc) to replace for some dynamic content in C#.
I would like to know, how can I put a content in a mergefield in docx ?
I don't know if MergeFields is the right way to do this or if there is another way. If you can suggest me, I appreciate!
PS: I have openxml referenced in my web application.
Edits:
private MemoryStream LoadFileIntoStream(string fileName)
{
MemoryStream memoryStream = new MemoryStream();
using (FileStream fileStream = File.OpenRead(fileName))
{
memoryStream.SetLength(fileStream.Length);
fileStream.Read(memoryStream.GetBuffer(), 0, (int) fileStream.Length);
memoryStream.Flush();
fileStream.Close();
}
return memoryStream;
}
public MemoryStream GenerateWord()
{
string templateDoc = "C:\\temp\\template.docx";
string reportFileName = "C:\\temp\\result.docx";
var reportStream = LoadFileIntoStream(templateDoc);
// Copy a new file name from template file
//File.Copy(templateDoc, reportFileName, true);
// Open the new Package
Package pkg = Package.Open(reportStream, FileMode.Open, FileAccess.ReadWrite);
// Specify the URI of the part to be read
Uri uri = new Uri("/word/document.xml", UriKind.Relative);
PackagePart part = pkg.GetPart(uri);
XmlDocument xmlMainXMLDoc = new XmlDocument();
xmlMainXMLDoc.Load(part.GetStream(FileMode.Open, FileAccess.Read));
// replace some keys inside xml (it will come from database, it's just a test)
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_customer", "My Customer Name");
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_title", "Report of Documents");
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_content", "Content of Document");
// Open the stream to write document
StreamWriter partWrt = new StreamWriter(part.GetStream(FileMode.Open, FileAccess.Write));
//doc.Save(partWrt);
xmlMainXMLDoc.Save(partWrt);
partWrt.Flush();
partWrt.Close();
reportStream.Flush();
pkg.Close();
return reportStream;
}
PS: When I convert MemoryStream to a file, I got a corrupted file. Thanks!
I know this is an old post, but I could not get the accepted answer to work for me. The project linked would not even compile (which someone has already commented in that link). Also, it seems to use other Nuget packages like WPFToolkit.
So I'm adding my answer here in case someone finds it useful. This only uses the OpenXML SDK 2.5 and also the WindowsBase v4. This works on MS Word 2010 and later.
string sourceFile = #"C:\Template.docx";
string targetFile = #"C:\Result.docx";
File.Copy(sourceFile, targetFile, true);
using (WordprocessingDocument document = WordprocessingDocument.Open(targetFile, true))
{
// If your sourceFile is a different type (e.g., .DOTX), you will need to change the target type like so:
document.ChangeDocumentType(WordprocessingDocumentType.Document);
// Get the MainPart of the document
MainDocumentPart mainPart = document.MainDocumentPart;
var mergeFields = mainPart.RootElement.Descendants<FieldCode>();
var mergeFieldName = "SenderFullName";
var replacementText = "John Smith";
ReplaceMergeFieldWithText(mergeFields, mergeFieldName, replacementText);
// Save the document
mainPart.Document.Save();
}
private void ReplaceMergeFieldWithText(IEnumerable<FieldCode> fields, string mergeFieldName, string replacementText)
{
var field = fields
.Where(f => f.InnerText.Contains(mergeFieldName))
.FirstOrDefault();
if (field != null)
{
// Get the Run that contains our FieldCode
// Then get the parent container of this Run
Run rFldCode = (Run)field.Parent;
// Get the three (3) other Runs that make up our merge field
Run rBegin = rFldCode.PreviousSibling<Run>();
Run rSep = rFldCode.NextSibling<Run>();
Run rText = rSep.NextSibling<Run>();
Run rEnd = rText.NextSibling<Run>();
// Get the Run that holds the Text element for our merge field
// Get the Text element and replace the text content
Text t = rText.GetFirstChild<Text>();
t.Text = replacementText;
// Remove all the four (4) Runs for our merge field
rFldCode.Remove();
rBegin.Remove();
rSep.Remove();
rEnd.Remove();
}
}
What the code above does is basically this:
Identify the 4 Runs that make up the merge field named "SenderFullName".
Identify the Run that contains the Text element for our merge field.
Remove the 4 Runs.
Update the text property of the Text element for our merge field.
UPDATE
For anyone interested, here is a simple static class I used to help me with replacing merge fields.
Frank Fajardo's answer was 99% of the way there for me, but it is important to note that MERGEFIELDS can be SimpleFields or FieldCodes.
In the case of SimpleFields, the text runs displayed to the user in the document are children of the SimpleField.
In the case of FieldCodes, the text runs shown to the user are between the runs containing FieldChars with the Separate and the End FieldCharValues. Occasionally, several text containing runs exist between the Separate and End Elements.
The code below deals with these problems. Further details of how to get all the MERGEFIELDS from the document, including the header and footer is available in a GitHub repository at https://github.com/mcshaz/SimPlanner/blob/master/SP.DTOs/Utilities/OpenXmlExtensions.cs
private static Run CreateSimpleTextRun(string text)
{
Run returnVar = new Run();
RunProperties runProp = new RunProperties();
runProp.Append(new NoProof());
returnVar.Append(runProp);
returnVar.Append(new Text() { Text = text });
return returnVar;
}
private static void InsertMergeFieldText(OpenXmlElement field, string replacementText)
{
var sf = field as SimpleField;
if (sf != null)
{
var textChildren = sf.Descendants<Text>();
textChildren.First().Text = replacementText;
foreach (var others in textChildren.Skip(1))
{
others.Remove();
}
}
else
{
var runs = GetAssociatedRuns((FieldCode)field);
var rEnd = runs[runs.Count - 1];
foreach (var r in runs
.SkipWhile(r => !r.ContainsCharType(FieldCharValues.Separate))
.Skip(1)
.TakeWhile(r=>r!= rEnd))
{
r.Remove();
}
rEnd.InsertBeforeSelf(CreateSimpleTextRun(replacementText));
}
}
private static IList<Run> GetAssociatedRuns(FieldCode fieldCode)
{
Run rFieldCode = (Run)fieldCode.Parent;
Run rBegin = rFieldCode.PreviousSibling<Run>();
Run rCurrent = rFieldCode.NextSibling<Run>();
var runs = new List<Run>(new[] { rBegin, rCurrent });
while (!rCurrent.ContainsCharType(FieldCharValues.End))
{
rCurrent = rCurrent.NextSibling<Run>();
runs.Add(rCurrent);
};
return runs;
}
private static bool ContainsCharType(this Run run, FieldCharValues fieldCharType)
{
var fc = run.GetFirstChild<FieldChar>();
return fc == null
? false
: fc.FieldCharType.Value == fieldCharType;
}
You could try http://www.codeproject.com/KB/office/Fill_Mergefields.aspx which uses the Open XML SDK to do this.

Categories

Resources