When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, April 30, 2008

Working with XML Data Using LINQ, a TreeView, and a ListView :: Editing Data

By Miroslav Kadera


Introduction


ASP.NET includes a variety of tools for displaying and editing XML documents. A previous article, Working with XML Data Using LINQ, a TreeView, and a ListView :: Displaying Data, showed how with a TreeView control, a ListView control, an XmlDataSource control, a LinqDataSource control, and about 50 lines of code we could create a web page that displayed the contents of a hierarchical XML file. Specifically, the page displayed the contents from a fictional employee phonebook, which allowed for an arbitrary number of employees nested within branches and departments. The TreeView and XmlDataSource controls displayed the various branches and departments, while the ListView and LinqDataSource controls displayed the employees belonging to the selected branch or department (or one of its subdepartments).

In addition to displaying data, the ListView and LinqDataSource controls can be augmented to edit, insert, and delete data. This article examines how to update the ListView and code to enable the visitor to add, edit, and delete employee phone records. By the conclusion of this article we will have constructed a web page that displays XML data and enables an end user to modify the data from a simple, easy-to-use web page interface. Read on to learn more!

- continued -

First Things First: Read the Displaying Data Article Before This One
This article builds upon the web page created in Working with XML Data Using LINQ, a TreeView, and a ListView :: Displaying Data. If you have not year read Working with XML Data Using LINQ, a TreeView, and a ListView :: Displaying Data, please do so before continuing with this article.

Configuring the ListView to Support Editing


The ListView control, new to ASP.NET version 3.5, displays a series of records from a data source using templates. In addition to displaying data, the ListView control can be used to insert, edit, and delete data. Like the GridView control, the ListView control allows for in-line editing. That is, a user can select a particular record to edit from the list of displayed records, make her changes, and then update the data. The ListView also offers inserting functionality.

The principles of editing records in a ListView are very simple. We have to create a special template for the item being edited; this template, EditItemTemplate, is used to render the item being edited by the user. Additionally, we need to add a Button, LinkButton, or ImageButton control (usually in the ItemTemplate) that has its CommandName property set to "Edit". When this button is clicked, a postback occurs and the ListView is rebound to its data source with this particular item rendered using its EditItemTemplate.

The ItemTemplate is straightforward enough: just add a LinkButton (or Button or ImageButton) with its CommandName property set to "Edit":

<ItemTemplate>
   <tr>
      <td>
         <asp:LinkButton ID="lnkEdit" runat="server" CommandName="Edit" Text="Edit" />
      </td>
      <td>
         <%# Eval("Name") %>
      </td>
      <td>
         <%# Eval("Telephone") %>
      </td>
   </tr>
</ItemTemplate>

Before we create the ListView's EditItemTemplate, however, there is a complication in our specific situation that we need to first address. Recall that the records displayed in the ListView may be located on different "levels" in our XML file (i.e. in various branches/departments). For example, when select a particular branch or department from the TreeView on the left, the ListView displays not only the employees in that selected branch or department, but all employees in descendent branches and departments as well. We need to be able to identify the correct branch or department an employee belongs to when saving the edited values back to the XML file.

We can solve this problem by remembering the XPath address of the Employee XML element when loading it in the ListView. To accomplish this we need to add a property to our anonymous type returned by the LINQ query. The text in red shows the new anonymous type member, XPathAddress:

var query = from employeeElement in parentElement.Descendants("Employee")
            select new
            {
                Name = employeeElement.Attribute("name").Value,
                Telephone = employeeElement.Attribute("telephone").Value,
                XPathAddress = getXPathAddress(employeeElement)
            };

We will later set this value as the CommandArgument property of the "Update" LinkButton in the ListView's EditItemTemplate.

As you can see from the LINQ query above, the XPathAddress member assigned the value returned by the getXPathAddress method, which we need to create within the ASP.NET page's code-behind class. The method will iteratively traverse the passed-in employeeElement structure to the root element, building the XPathAddress value as it walks up the tree:

private string getXPathAddress(XElement element)
{
   XElement cElement = element;
   string xPath = "";

   // We will iteratively go through the structure to the root element
   // The loop will stop at the root element (we don't include the root
   // element in the address !!)
   while (cElement.Parent != null)
   {
       xPath = String.Format("/*[@id='{0}']",
               cElement.Attribute("id").Value)
                + xPath;

       cElement = cElement.Parent;
    }

    return xPath;
}

Now that we know the XPath expression for each employee object, we can create the EditItemTemplate. As you can see, the Update LinkButton's CommandArgument property is assigned the XPathAddress value returned by the LinqDataSource. We will need to use this value in code to update the apporpriate employee record. Also note that instead of displaying the employee's name and phone number values in a Label, we use TextBox Web controls instead.

<EditItemTemplate>
   <tr>
      <td>
         <asp:LinkButton ID="lnkUpdate" runat="server"   
            CommandName="XUpdate"
            CommandArgument="<%# Eval("XPathAddress") %>"
            Text="Update" />,
         <asp:LinkButton ID="lnkCancelEdit" runat="server"
            CommandName="Cancel"
            Text="Cancel" />
      </td>
      <td>
         <asp:TextBox ID="txtEditName" runat="server"
                   Text="<%# Eval("Name") %>" />
      </td>
      <td>
         <asp:TextBox ID="txtEditTelephone" runat="server"
                   Text="<%# Eval("Telephone") %>" />
      </td>
   </tr>
</EditItemTemplate>

You may be wondering why we used the CommandName value of "XUpdate" instead of "Update". If we set the CommandName to "Update", the ListView will try to update the changes automatically by calling the data source control's Update method. We have mapped the data ourselves (in our LinqDataSource control's Selecting event handler), so our LinqDataSource can't serve as the updating source in this case. We therefore have to write a method ourselves to save changes.

Saving the Editing Record Back to the XML File


We need to execute code when the "XUpdate" LinkButton has been clicked. The ListView's ItemCommand event is raised whenever a button with a CommandName property is clicked. Therefore, we need to create an event handler for this event. Keep in mind that this event handler will execute when any command button in the ListView is clicked; this includes the sorting and paging interface buttons. Consequently, it is imperative that we check the passed in e.CommandName value equals "XUpdate" before proceeding with our updating logic.

When the "XUpdate" button has been clicked, we need to save the changes and return the ListView to its pre-editing state. The following code illustrates this functionality. Note that the data is saved by the saveChanges method; we will create this method in a moment.

protected void lvwEmployees_ItemCommand(object sender, ListViewCommandEventArgs e)
{
   switch (e.CommandName)
   {
      // Update the XML file after editation
      case "XUpdate":
         saveChanges((string)e.CommandArgument, e.Item);

         lvwEmployees.EditIndex = -1;
         lvwEmployees.DataBind();

         break;
   }
}

Our final task for editing records is to create the saveChanges method, which saves the changes back to the XML file. As the above code shows, the saveChanges method is passed two input parameters:

  • The XPath address of the element being changed (recall that we assigned this XPathAddress value to the Update button's CommandArgument property)
  • The ListViewItem being edited. Our method will search TextBoxes with new values in this ListViewItem
The saveChanges method loads the Employees XML element from the source file as an XElement object. The values of the XElement object are then modified to correspond with the user's entries. Finally, the updated XElement object is saved back to the XML file.

protected void saveChanges(string xPath, ListViewItem lvwItem)
{
   string fileName = MapPath("PhoneBook.xml");

   XElement rootElement = XElement.Load(fileName);
   XElement itemElement = rootElement.XPathSelectElement(xPath);

   itemElement.SetAttributeValue("name",
         ((TextBox)lvwItem.FindControl("txtEditName")).Text);

   itemElement.SetAttributeValue("telephone",
         ((TextBox)lvwItem.FindControl("txtEditTelephone")).Text);

   rootElement.Save(fileName);
}

That's all there is to it! At this point a user can edit any existing employee phone record, changing their name, telephone number, or both.

Deleting Employee Phone Records


Deleting employee phone records works on a similar principle. We need to remember the XPathAddress for the employee and pass this to the event handler responsible for deleting the record. As with editing, we need to add a Delete LinkButton (or Button or ImageButton) to the ListView's ItemTemplate. Set its CommandName property to "XDelete" and its CommandArgument property to the XPathAddress value of the element to delete.

<asp:LinkButton ID="lnkDelete" runat="server"
      CommandName="XDelete"
      CommandArgument='<%# Eval("XPathAddress") %>'
      Text="Delete" />

In the ListView's ItemCommand event handler we need to handle the "XDelete" command. Like with the "XUpdate" command, we will call a helper method (deleteItem), passing it the XPath expression, and then return the ListView to its pre-editing state.

case "XDelete":
   deleteItem((string)e.CommandArgument);

   lvwEmployees.EditIndex = -1;
   lvwEmployees.DataBind();

   break;

The deleteItem method loads an XElement object based on the supplied XPath expression, deletes it, and then saves the contents of the XML file back to disk.

protected void deleteItem(string xPath)
{
   string fileName = MapPath("PhoneBook.xml");

   XElement rootElement = XElement.Load(fileName);

   XElement itemElement = rootElement.XPathSelectElement(xPath);

   itemElement.Remove();
   rootElement.Save(fileName);
}

Adding New Employee Phone Records


Our final task is to enable the end user to add new employee phone records to the selected branch or department. Inserting an item from the ListView control is similar to editing an item in that both interfaces are defined through templates. To define the interface used for inserting, use the InsertItemTemplate. This template can be shown as the first item in the ListView or as the last item.

The InsertItemTemplate (shown below) looks similar to the EditItemTemplate in that it contains a TextBox Web control for the employee's name and telephone number. Instead of an Update button, we have a Save button (whose CommandName property is set to "XSave").

<InsertItemTemplate>
   <tr>
      <td style="border-bottom: solid black 1px;">
         <asp:LinkButton ID="lnkSave" runat="server" CommandName="XSave" Text="Save" />,
         <asp:LinkButton ID="lnkCancelInsert" runat="server" CommandName="Cancel" Text="Cancel" />
      </td>
      <td style="border-bottom: solid black 1px;">
          <asp:TextBox ID="txtInsertName" runat="server" Text='<%# Eval("Name") %>' />
      </td>
      <td style="border-bottom: solid black 1px;">
         <asp:TextBox ID="txtInsertTelephone" runat="server" Text='<%# Eval("Telephone") %>' />
      </td>
   </tr>
</InsertItemTemplate>

Like with the "XUpdate" and "XDelete" commands, we need to handle the "XSave" command in the ListView's ItemCommand event handler. After saving the item (via a call to saveNewItem), we need to rebind the data to the ListView so that the just-added item appears in the list.

case "XSave":
   saveNewItem(e.Item);

   lvwEmployees.DataBind();
   break;

The saveNewItem method is a bit more complicated than the saveChanges or deleteItem methods. Part of the challenge stems from the fact that each element in the XML file has an id attribute with a unique identifier for that "level." So in adding a new element we need to choose an appropriate id value.

The saveNewItem method performs the following tasks:

  1. Load the root element (like in other methods)
  2. Get the XPathAddress of the selected element in the TreeView and find the element in the XML file
  3. Get the maximum ID value from all elements in the parent element
  4. Create a new XElement object
  5. Add the new element to its parent and save the file
The code follows:

protected void saveNewItem(ListViewItem lvwItem)
{
   string fileName = MapPath("PhoneBook.xml");

   XElement rootElement = XElement.Load(fileName);

   string xPath = tvwPhoneBook.SelectedNode.DataPath.Substring(tvwPhoneBook.Nodes[0].DataPath.Length);

   XElement parentElement = rootElement.XPathSelectElement(xPath);

   int maxId = parentElement.Elements().Max(c => int.Parse(c.Attribute("id").Value));

   XElement newElement = new XElement("Employee",
            new XAttribute("id", (maxId + 1).ToString()),
            new XAttribute("name", ((TextBox)lvwItem.FindControl("txtInsertName")).Text),
            new XAttribute("telephone", ((TextBox)lvwItem.FindControl("txtInsertTelephone")).Text));

   parentElement.Add(newElement);

   rootElement.Save(fileName);
}

That's all, folks! With relatively small effort and a few lines of code we've created a fully-functional ASP.NET application for editing structured XML data. The main idea, on which the application is based, is the connection of TreeView and ListView, which enabled us to edit tabular data stored in more hierarchical XML structures. The bulk of the work is done for us by the TreeView, ListView, DataPager and LinqDataSource controls. All we had to do was write the code dealing with the location of record in various levels of the XML structure.

The ListView control offers inserting, editing, and deleting capabilities.

Happy Programming!

  • By Miroslav Kadera


    Further Reading


  • Working with XML Data Using LINQ, a TreeView, and a ListView :: Displaying Data
  • Quickly Editing an XML File
  • LINQ to XML - 5 Minute Overview
  • Using ASP.NET 3.5's ListView and DataPager Controls
  • Attachments


  • Download the code used in this article



  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article