1. Original Entry + Comments2. Write a Comment3. Preview Comment
New comments for this entry are disabled.


January 08, 2006  |  Inserting into an XML file (and deleting)  |  8476 hit(s)

Last time I played around with updating an XML file using an XmlDataSource control, a GridView control, and a DetailsView control. Inserting into an XML file is similar, in that guess what, you get to do all the work. One decision I made was to have a button on the page that would explictly put the DetailsView control into insert mode. My use of the DetailsView control is therefore maybe not exactly as it might have been designed:
  • Before any row in the GridView control is selected, the DetailsView control is invisible.
  • When you select a row in the GridView control, I make the DetailsView control visible, but also put it into edit mode. When you update the selected row, I disappear the DetailsView control again.
  • To insert a new record, I click a button, which displays the DetailsView control in insert mode. When I click Save, the DetailsView control disappears again.
Make sense? IOW, I'm programmatically controlling the mode in which the DetailsView control appears, rather than using any built-in functionality for that. And I'm really only displaying it in two modes, edit and insert.

Here's the code to put the DetailsView control into insert mode:
Protected Sub buttonInsert_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
XmlDataSource2.Data = "" ' Clear old data from control
DetailsView1.DataBind()
DetailsView1.Visible = True
DetailsView1.ChangeMode(DetailsViewMode.Insert)
End Sub
If I don't set the Data property (or do something like that), the DetailsView control shows up with whatever record was last edited or inserted.

When you click the Update button, here's what happens:
Protected Sub DetailsView1_ItemInserting(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.DetailsViewInsertEventArgs)
Dim newQuote As String = e.Values(0)
Dim newAuthor As String = e.Values(1)


Dim quotesXmlDoc As New System.Xml.XmlDocument
Dim xmlFileName As String = XmlDataSource2.DataFile
xmlFileName = xmlFileName.Replace("~", Server.MapPath("~"))
xmlFileName = xmlFileName.Replace("/", "\")
quotesXmlDoc.Load(xmlFileName)
If quotesXmlDoc Is Nothing Then
Response.Write("Xml doc is nothing")
Exit Sub
End If


' Count #/existing nodes to set new key
Dim lastElement As XmlElement = quotesXmlDoc.DocumentElement.LastChild
Dim newQuoteID As Integer = CInt(lastElement("quoteId").InnerText) + 1


' Create new Quotation element and add children elements to it
Dim newQuoteElement As XmlElement = quotesXmlDoc.CreateElement("Quotation")


Dim quoteIdElement As XmlElement = quotesXmlDoc.CreateElement("quoteId")
quoteIdElement.InnerText = newQuoteID.ToString()
newQuoteElement.AppendChild(quoteIdElement)


Dim quoteElement As XmlElement = quotesXmlDoc.CreateElement("quote")
quoteElement.InnerText = newQuote
newQuoteElement.AppendChild(quoteElement)


Dim authorElement As XmlElement = quotesXmlDoc.CreateElement("author")
authorElement.InnerText = newAuthor
newQuoteElement.AppendChild(authorElement)


quotesXmlDoc.DocumentElement.AppendChild(newQuoteElement)
' Write out updated file
quotesXmlDoc.Save(xmlFileName)


e.Cancel = True


DetailsView1.ChangeMode(DetailsViewMode.ReadOnly)
DetailsView1.Visible = False
End Sub
It's similar to updating, with a couple of small differences. (Other than the obvious difference that I'm adding a new element rather than updating an existing one.) One difference is that I have to come up with a new quoteId value -- that is, a unique key for the new quotation. I use the somewhat cheesy approach of reading the ID value of the last quote in the file and then incrementing that. The assumption, of course, is that the quotes are all in numeric order. That happens to be true in this file, but is hardly a robust algorithm for general use.

This time I got the path of the XML file from the XmlDataSource control and fixed it up by subbing Server.MapPath("~") for the ~ returned as part of the property string. Again, this is because the various System.Xml methods don't know what ~ means.

After writing out the XML file, I cancel the insert method, else the XmlDataSource control throws a not-supported exception, because it doesn't support any update methods.

To complete the triptych of update operations, I added code to be able to delete a quotation. (This has proved handy primarily for deleting the various test quotes I put in there.) I don't need the DetailsView control to delete; I can do that directly from the GridView control by adding a CommandField to the GridView control like this:
<asp:CommandField ShowDeleteButton="True" />
To actually delete the quotation, I used this code:
Protected Sub GridView1_RowDeleting(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs)
XmlDataSource2.Data = "" ' Clear old data from control
DetailsView1.DataBind()
DetailsView1.Visible = False


Dim quoteId As String = e.Keys(0).ToString
Dim quotesXmlDoc As New System.Xml.XmlDocument
Dim xmlFileName As String = XmlDataSource2.DataFile
xmlFileName = xmlFileName.Replace("~", Server.MapPath("~"))
xmlFileName = xmlFileName.Replace("/", "\")
quotesXmlDoc.Load(xmlFileName)


Dim quoteNode As XmlElement
quoteNode = quotesXmlDoc.DocumentElement.SelectSingleNode( _
String.Format("//Quotation[quoteId='{0}']", quoteId))
quotesXmlDoc.DocumentElement.RemoveChild(quoteNode)
quotesXmlDoc.Save(xmlFileName)


GridView1.DataBind()
e.Cancel = True
End Sub
Much like updating -- get key, read XML file, locate node using an XPath expression. This time, call RemoveChild, and poof, it's gone.

I am having one odd problem that I haven't quite resolved. When I update a quote or insert a new one, I call DataBind to refresh the display in the GridView control, and that works great. However, when I do the same thing after deleting a record, the deleted record stubbornly stays visible. If I page away from that page and page back, all is well. So there's something I'm not understanding about re-display after a delete in the GridView contol. When I have that one sussed out, I'll note something here.

One more thing. When I insert a new record, it's always added to the end of the XML file. I decided early on that it would be handy therefore to have a link in the pager that would take me directly to the last page, no matter where I was otherwise. Wow, this turned out to be harder to figure out than I thought it would be. I'll make a separate entry for that, since this is long enough.




Jeff   07 Dec 06 - 1:39 PM

Is the delete problem a caching issue? I had the same problems with updating and ended up setting caching to false on the xmldatasources.

 
mike   07 Dec 06 - 1:48 PM

A caching problem sounds right -- and interestingly, this just came up at work the other day. I'll give it a try and report back.

 
mike   23 Dec 06 - 4:24 PM

Ok, finally got around to testing this. Turns out that no, it's not caching -- I'm handling deletion manually and then manually rebinding. EnableCaching for both XML data source controls is in any event set to false.

It's the weirdest thing -- if I use a separate button handler to rebind the grid, then it works perfectly. It's only if I rebind in the deletion handler, immediately after deleting, that it doesn't seem to work.


 
MokeyP   09 May 07 - 10:13 PM

Better late than never. Enabling caching or viewstate can produce these types of results when saving xml files to disk. See http://msdn2.microsoft.com/en-us/library/494y92bs.aspx for more details on this..

 
mike   10 May 07 - 12:41 AM

@MokeyP

Thanks for the suggestion. In fact, both viewstate and caching are disabled for both XMLDataSource controls on the page, and viewstate is disabled for the GridView control as well. Still a mystery ...


 
BryanAx   09 Jul 07 - 8:16 AM

Did you ever find a solution to this? I'm running into the same issue. I know the item is being deleted from the xml -see that debugging. But when I try to rebind, no luck. Old record is still showing up for some reason...

 
mike   09 Jul 07 - 9:10 AM

No, never did find a solution. I'm still manually refreshing the browser, which does the trick for my particular situation.

I'll see if I can get someone to have a look again.


 
BryanAx   09 Jul 07 - 9:19 AM

My workaround was to skip using a CommandField, and instead just use a ButtonField with a Command of Delete. Works fine in this situation, so it is some kind of strangeness when using the CommandField with ShowDeleteButton=True.

It always freaks me out when something doesn't work as I expect - glad to know that I'm not the only one who ran into this. Very odd behavior. I'm also somewhat disappointed with how hard it is to get the XmlDataSource to work properly - there are lots of times when Xml is a valuable data store, but it sure seemed like a lot of work to get it to function correctly when wanting to add/update/delete with the GridView control.



 
Sam Banerjee   20 Nov 07 - 9:50 AM

I was having this problem too, and changing from CommandField, to a ButtonField with a Command of Delete didn't help. In my case, I realized that my gridview was in an Ajax UpdatePanel, so I needed a statement:

<ajax:AsyncPostBackTrigger ControlID="BusinessGroupListGV" EventName="RowCommand"/>

in the .aspx file. This fied the problem.