• No results found

AMIDALA\ASPNET 7 Click the OK button.

Reading an XML File Using XmlTextReader

AMIDALA\ASPNET 7 Click the OK button.

8. Click the OK button.

When you run this example, you should get a Web page that looks like the one shown in

Figure 8-5. Exciting, don't you think?

Figure 8-5: The WriteXML Web page

Inserting and Updating an XML File

You can now read and write an XML document, so let's take the final plunge and go ahead and complete the ways of working with XML documents. All that's left to cover is inserting and updating.

Just to simplify things, you will work with the minimal XML file shown in Listing 8-4. Basically, I removed the Articles elements from the original Content.xml. You should put the XML document in the XMLFiles directory that you created earlier in the Solution Explorer.

Listing 8-4: Authors.xml

<?xml version="1.0" encoding=" utf-8"?>

<Authors> <Author>

<FirstName>Bill</FirstName> <LastName>Doors</LastName> </Author>

<Author>

<FirstName>Larry</FirstName> <LastName>Ellidaughter</LastName> </Author>

<Author>

<FirstName>Stephen</FirstName> <LastName>Fraser</LastName> </Author>

</Authors>

The goal of this example is to insert a new author after Ellidaughter. You will follow that with updating my last name. For more grins and giggles, I will show a third way of reading an XML file.

First, you will add a new Web form to your project. Name the Web form

UpdateXML.aspx. Then design a Web page consisting of titles and two list boxes, changing the (ID) of these list boxes to lbBefore and lbAfter. Finally, bring up the code for UpdateXML in the main edit window.

As always, the first thing you need to do is add namespaces. In this example, you will need two. You are obviously going to need the XML namespace, but to implement the third method of how to read an XML document, you need the IO namespace as well. The code, as you can see, is nothing you shouldn't already know.

using System.Web.UI.HtmlControls; using System.IO;

using System.Xml; ...

With the namespaces squared away, let's open up the authors.xml file and assign an

XmlTextReader to it. Next, create a new XmlDocument and load it with

XmlTextReader. Finally, close the authors.xml file so that it can be opened later with the inserted and updated authors.xml file.

if (!IsPostBack) {

XmlReader reader = new XmlTextReader(

File.OpenRead(Server.MapPath("XMLFiles\\Authors.xml")));

XmlDocument doc = new XmlDocument(); doc.Load(reader);

reader.Close(); ...

Before I get to inserting and updating, you are going to look at a third way of reading an XML file. This method only needs the functionality provided by the XmlDocument and

XmlNodeList classes.

This method provides the capability to do simple searches in tag names and then load an array with the retrieved values. In this example, I note that there is a first name for every last name, so you can do a search on both and then concatenate the retrieved values into the list box, displaying the author's full name.

XmlNodeList fnames = doc.GetElementsByTagName("FirstName"); XmlNodeList lnames = doc.GetElementsByTagName("LastName");

for (int i = 0; i < lnames.Count; i++) {

lbBefore.Items.Add(fnames[i].InnerText + ""+ lnames[i].InnerText); }

You will use this same code after you do your inserts and updates. The only difference will be the name of the list box you will populate.

fnames = doc.GetElementsByTagName("FirstName"); lnames = doc.GetElementsByTagName("LastName");

for (int i = 0; i < lnames.Count; i++) {

lbAfter.Items.Add(fnames[i].InnerText + ""+ lnames[i].InnerText); }

The steps for inserting a piece of XML into another piece of XML are hardly difficult. The following method of inserting XML after a sibling is one way, but there are many others. Some of the other, more common methods of inserting XML are before another sibling, after all children, and before all children.

The first thing you need to do is create the XML you want to insert. The easiest way to do so is simply to create the entire piece as a string and insert it into an

XmlDocumentFragment, as you see in this piece of code:

XmlDocumentFragment newAuthor = doc.CreateDocumentFragment();

newAuthor.InnerXml=("\n <Author>\n" +

" <FirstName>John</FirstName>\n" + " <LastName>Doe</LastName>\n" + " </Author>\n");

You might notice that you can embed new line characters using the escape code "/n". If you don't add new line characters of your own, the fragment inserted will end up as one long line of XML. This is not a problem for a computer, but humans like nice formatting. Go figure.

Admittedly, the following code is a bit of a hack, but it does show the basics of how to insert XML. First, you locate the sibling node after which you want to insert. Then you use the InsertAfter() method to squeeze it in. In the example, you are placing the new XML fragment after the second Author element.

XmlNodeList nodes = root.GetElementsByTagName("Author"); root.InsertAfter(newAuthor,nodes.Item(1));

To insert the fragment before the second Author, you would simply change the last line as follows:

root.InsertBefore(newAuthor,nodes.Item(1));

The process to update an XML file is only slightly trickier. You must navigate to the node you want to change and then replace the old values in the node with the new. In the example, I get an array of all Authors, and then I navigate through all of them, getting all of the LastName elements. There is only one, but this shows how you can find a node if there were more. Finally, I check if the LastName contains my last name: Fraser. If it does, I replace it with TheAuthor. As you can see, the code is quite easy:

nodes = root.GetElementsByTagName("Author");

for (int i = 0; i < nodes.Count; i++) {

XmlNodeList authornodes =

((XmlElement)(nodes.Item(i))).GetElementsByTagName("LastName");

for (int j = 0; j < authornodes.Count; j++) { if (authornodes[j].InnerText.Equals("Fraser")) { authornodes[j].InnerText = "TheAuthor"; } } }

The last thing you need to do is save the XML document to disk. If you don't, all the changes you made will disappear when you exit the program. I do this purposely in this example, as you may note in the final version. This code is commented out in the final version.

StreamWriter writer = new StreamWriter(

File.OpenWrite(Server.MapPath("XMLFiles\\Authors.xml"))); doc.Save(writer);

writer.Close();

Listing 8-5 shows the entire UpdateXML.cs so that you can make sure you entered it correctly.

Listing 8-5: The UpdateXML Codebehind

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.IO; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.Xml; namespace Ch08Examples {

public class UpdateXML : System.Web.UI.Page

{

protected System.Web.UI.WebControls.ListBox lbAfter;

protected System.Web.UI.WebControls.ListBox lbBefore;

private void Page_Load(object sender, System.EventArgs e)

{

if (!IsPostBack)

{

XmlReader reader = new XmlTextReader(

File.OpenRead(Server.MapPath("XMLFiles\\Authors.xml")));

XmlDocument doc = new XmlDocument();

doc.Load(reader);

reader.Close();

// Displaying XML Document before insert & update

XmlNodeList fnames = doc.GetElementsByTagName("FirstName"); XmlNodeList lnames = doc.GetElementsByTagName("LastName");

for (int i = 0; i < lnames.Count; i++) {

lbBefore.Items.Add(fnames[i].InnerText + ""+ lnames[i].InnerText);

// Inserting

XmlDocumentFragment newAuthor = doc.CreateDocumentFragment(); newAuthor.InnerXml=("\n <Author>\n" +

" <FirstName>John</FirstName>\n" + " <LastName>Doe</LastName>\n" + " </Author>\ n");

//insert the new author after 2nd author XmlElement root = doc.DocumentElement;

XmlNodeList nodes = root.GetElementsByTagName("Author"); root.InsertAfter(newAuthor,nodes.Item(1));

// Updating

// Get New node list with inserted author

nodes = root.GetElementsByTagName("Author");

for (int i=0; i < nodes.Count; i++) {

XmlNodeList authornodes = ((XmlElement)(nodes.Item(i))).

GetElementsByTagName("LastName"); for (int j=0; j < authornodes.Count; j++) { if (authornodes[j].InnerText.Equals("Fraser")) { authornodes[j].InnerText = "TheAuthor"; } } }

// Normally you would save the document but we want to // be able to re-run this

// StreamWriter writer = new StreamWriter(

// File.OpenWrite(Server.MapPath("XMLFiles\\Authors.xml"))); // doc.Save(writer);

// writer.Close();

// Reading XML Document after insert & update fnames = doc.GetElementsByTagName("FirstName"); lnames = doc.GetElementsByTagName("LastName");

for (int i = 0; i < lnames.Count; i++) { lbAfter.Items.Add(fnames[i].InnerText + ""+ lnames[i].InnerText); } } }

#region Web Form Designer generated code override protected void OnInit(EventArgs e) {

base.OnInit(e); }

private void InitializeComponent() {

this.Load += new System.EventHandler (this.Page_Load); }

#endregion }

}

After you enter, compile, and run this program, you should get a Web page that looks similar to the one shown in Figure 8-6.

Figure 8-6: The UpdateXML Web page

Creating a NavBar Using XML

Let's finish off all the background material needed to build a CMS with an example you can actually use. Almost all Web sites have a NavBar, and implementing one (using XML to store the navigation structure) is really quite easy.

Actually, the hard part of implementing an XML NavBar is not the XML code at all. It is the intrinsic Table control code that complicates things. It would be a simple thing to write the <table> HTML in the CodeBehind C# file, but then you would not be separating the code and HTML as I promised in the beginning of the book.

Before you get to the coding of the NavBar, let's take a quick look at the XML file that stores the Web site navigation. As you can see in Listing 8-6, the structure is very simple.

Menu 1 Menu Name Menu Item 1 Name Link Menu Item 2 ... Menu Item n ... Menu 2 ... Menu n ...

The structure is a list of menus. Each menu has a menu name followed by a list of menu items. Each menu item has a name and a link. Listing 8-7 shows the navigation structure of the example. Note that the first two menus contain nonexistent Web pages, and the final menu has menu items pointing to the Web pages developed in this chapter. As with all the other examples, you should place this XML file in the XMLFiles directory in the Solution Explorer.

Listing 8-7: MainMenu.xml

<?xml version="1.0" encoding=" utf-8" ?>

<MainMenu> <Menu>

<MenuName>Users</MenuName> <MenuItem>

<Name>User Summary</Name> <Link>Users.aspx</Link> </MenuItem>

<MenuItem>

<Name>Create User</Name> <Link>CreateUser.aspx</Link> </MenuItem>

<MenuItem>

<Link>EditUser.aspx</Link> </MenuItem>

<MenuItem>

<Name>Remove User</Name> <Link>RemoveUser.aspx</Link> </MenuItem>

</Menu> <Menu>

<MenuName>Groups</MenuName> <MenuItem>

<Name>Group Summary</Name> <Link>Groups.aspx</Link> </MenuItem>

<MenuItem>

<Name>Create Group</Name> <Link>CreateGroup.aspx</Link> </MenuItem>

<MenuItem>

<Name>Edit Group</Name> <Link>EditGroup.aspx</Link> </MenuItem>

<MenuItem>

<Name>Group Members</Name> <Link>MemberGroup.aspx</Link> </MenuItem>

<MenuItem>

<Name>Remove Group</Name> <Link>RemoveGroup.aspx</Link> </MenuItem>

</Menu> <Menu>

<MenuName>Test Items</MenuName> <MenuItem>

<Name>Read XML</Name> <Link>ReadXML.aspx</Link> </MenuItem>

<MenuItem>

<Name>Write XML</Name> <Link>WriteXML.aspx</Link> </MenuItem>

<MenuItem>

<Name>Update XML</Name> <Link>UpdateXML.aspx</Link> </MenuItem>

</MainMenu>

To pretty up the NavBar, I decided to add three images:

§ Minus.gif: Showing that the menu is expanded

§ Plus.gif: Showing that the menu is contracted

§ Blank.gif: Showing that the menu item doesn't expand or contract

For this example, all the images were put into their own directory called Images, oddly enough, which we created in the Solution Explorer. You can find a copy of the images I used on the Apress Web site in the Downloads section

(www.apress.com/downloads/downloadPrompt.html). Alternatively, you can use almost any small image you like.

The design of the NavBar Web page, which I call Menu.aspx, is easy enough. All it contains is one intrinsic Table control from the Toolbox, with an (ID) of tblMenu. It is livened up a bit by changing the background color to tan and the font to bold.

The first thing you do in the code is the same as always: You add the namespaces needed. In this case, you need to add both the XML and IO namespaces, as shown in the following code:

using System.Web.UI.HtmlControls; using System.IO;

using System.Xml; ...

Loading the XML file into an XMLDocument was covered in the third example. The only thing worth noting is that this code is run every time the page is loaded. This is because session state is not preserved between calls. It is easier to simply rebuild the menu from scratch and expand on the clicked menu item. Instead of remembering what was expanded, contract it and then expand the clicked menu item.

private void Page_Load(object sender, System.EventArgs e) {

XmlReader reader = new XmlTextReader(

File.OpenRead(Server.MapPath("XMLFiles\\MainMenu.xml")));

XmlDocument doc = new XmlDocument(); doc.Load(reader);

reader.Close(); ...

Next, you use a little trick by having prior knowledge of the possible return values of

Request.QueryString. Whenever you click an expanding menu item, Menu.aspx is recalled with the number of the menu selected as an Expand=n parameter. You will later take that number and expand all its menu items. The trick is that the

Request.QueryString property, if accessed with an unknown or missing parameter, returns null. Because this is the case, you can check the return value for null and, if found, initialize the number of the menu to expand to a nonvalid menu value: -1. If you have a valid Expand=n parameter, use it. The code snippet showing this follows: string expand = Request.QueryString["Expand"];

if (expand == null) ExpandWhich = -1; else

ExpandWhich = Convert.ToInt16(expand);

The code that builds the menu, at first glance, seems quite complex, but actually it is fairly simple. First, you declare a couple of variables that you will use repeatedly, so you just declare them here once. Then you create an array of all the menu XmlNodes. TableCell cell;

HyperLink link;

XmlNodeList Menus = doc.GetElementsByTagName("Menu");

Next, you cycle through all the menus found in the XML file.

for (int i = 0; i < Menus.Count; i++) {

For each menu, you are going to need to add a new row in your Table control. You do this by creating a new empty row and then adding it to the tblMenu.

TableRow row = new TableRow(); tblMenu.Rows.Add(row);

When you come to the menu item that needs to be expanded, which can be easily determined by comparing the current row number with the value passed in the

Expand=n parameter, you branch to the code to handle it. if (ExpandWhich == i)

{

Because you know that this menu item is to be expanded, you can put the minus.gif into the first column of the row. You do this by creating an empty cell and then creating an image control. You then place the image in the cell, which is in turn placed in the row.

Because the creation of images is done three times in the code, you probably want to make it a method of its own.

cell = new TableCell();

cell.Width = Unit.Percentage(1.0); System.Web.UI.WebControls.Image image = new System.Web.UI.WebControls.Image(); image.ImageUrl = "Images/minus.gif"; image.Width = Unit.Pixel(11); image.Height = Unit.Pixel(11); image.BorderWidth = Unit.Pixel(0); cell.Controls.Add(image); row.Cells.Add(cell);

Next, you create a literal control with the name of the menu that has been expanded. This is pretty simple. Create the literal control, place it in a new cell, and place the cell in the row.

LiteralControl lit =

new LiteralControl(Menus[i].FirstChild.InnerText);

cell = new TableCell();

cell.Width = Unit.Percentage(99.0); cell.Controls.Add(lit);

row.Cells.Add(cell);

Because this is the expanded row, you now fetch all the menu items out of the

MainMenu.xml. Then for each menu item, you create a row and place a blank.gif image in the first cell.

XmlNodeList MenuNodes = Menus[i].ChildNodes;

// start at 1 since 0 is the Menu Name

for (int j = 1; j < MenuNodes.Count; j++) {

row = new TableRow(); tblMenu.Rows.Add(row);

cell = new TableCell();

cell.Width = Unit.Percentage(1.0);

image = new System.Web.UI.WebControls.Image(); image.ImageUrl = "Images/blank.gif"; image.Width = Unit.Pixel(11); image.Height = Unit.Pixel(11); image.BorderWidth = Unit.Pixel(0); cell.Controls.Add(image); row.Cells.Add(cell);

Each menu item needs to be associated with a hyperlink to the page to which it will be jumping. Simply build a new HyperLink and place in it the name you assigned to it and its hyperlink as specified in the MainMenu.xml. Then create a new cell, place the hyperlink in the cell, and place the cell in the row.

link = new HyperLink();

link.Text = MenuNodes[j].ChildNodes[0].InnerText; link.NavigateUrl = MenuNodes[j].ChildNodes[1].InnerText;

cell = new TableCell();

cell.Width = Unit.Percentage(99.0); cell.Controls.Add(link);

} }

If this menu is not expanded, you execute this branch. First, you create a new row and then place a plus.gif in the first column.

else {

cell = new TableCell();

cell.Width = Unit.Percentage(1.0); System.Web.UI.WebControls.Image image = new System.Web.UI.WebControls.Image(); image.ImageUrl = "Images/plus.gif"; image.Width = Unit.Pixel(11); image.Height = Unit.Pixel(11); image.BorderWidth = Unit.Pixel(0); cell.Controls.Add(image); row.Cells.Add(cell);

Finally, you come to the magic of this NavBar. You create a hyperlink back to itself (Menu.aspx) with an Expand=n parameter, assigning the value of the current menu to n

. What this does, when selected, is cause the Expand=n parameter to be passed to the next execution of the Menu.aspx so that it can be expanded.

link = new HyperLink();

link.Text = Menus[i].FirstChild.InnerText; link.NavigateUrl = "Menu.aspx?Expand=" + i;

cell = new TableCell();

cell.Width = Unit.Percentage(99.0); cell.Controls.Add(link);

row.Cells.Add(cell); }

}

So you can check to make sure you didn't miss anything, Listing 8-8 shows the entire code for Menu.cs.

Listing 8-8: Menu.cs