Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
468 C H A P T E R 1 2 ■ X M L
Figure 12-16. Displaying the results of a query through XML and XSLT
Remember that when you interact with your data as XML, all the customary database-oriented concepts such as relationships and unique constraints go out the window. The only reason you should interact with your DataSet as XML is if you need to perform an XML-specific task. You shouldn’t use XML manipulation to replace the approaches used in earlier chapters to update data. In most cases, you’ll find it easier to use advanced controls such as the GridView, rather than creating a dedicated XSL stylesheet to transform data into the HTML you want to display.
Executing an XML Query
SQL Server 2000 and later provide built-in support for XML. You can execute any query and return the results as an XML fragment by adding the FOR XML clause to your query. This feature is completely separate from the XML features of the DataSet. However, it gives you another way to retrieve data from one or more tables in a database and work with it as XML.
■Note An XML fragment contains valid XML syntax, but isn’t necessarily a valid document of its own. Usually, this is because there is no root element. An XML document requires all elements to be nested in a single root element.
The FOR XML clause supports a specialized syntax that allows you to specify the exact structure and naming of the resulting document. However, this syntax doesn’t conform to any XML standard, and it’s notoriously messy. As a result, XML queries often use one of the two default XML representations that SQL Server provides. The first is FOR XML AUTO.
C H A P T E R 1 2 ■ X M L |
469 |
For example, if you execute this query:
SELECT FirstName, LastName FROM Employees FOR XML AUTO
you’ll receive results like this:
<Employees FirstName="Nancy" LastName="Davolio"/> <Employees FirstName="Andrew" LastName="Fuller"/> <Employees FirstName="Janet" LastName="Leverling"/>
...
As you can see, each record is a separate element, with all the fields as attributes. The other easy option is FOR XML AUTO, ELEMENTS. Here’s an example:
SELECT FirstName, LastName FROM Employees FOR XML AUTO, ELEMENTS
In this case, the data you’ll receive uses separate elements for each row and each field, as shown here:
<Employees>
<FirstName>Nancy</FirstName>
<LastName>Davolio</LastName>
</Employees>
<Employees>
<FirstName>Andrew</FirstName>
<LastName>Fuller</LastName>
</Employees>
<Employees>
<FirstName>Janet</FirstName>
<LastName>Leverling</LastName>
</Employees>
■Tip You can also fine-tune the format in much more painstaking detail using the FOR XML EXPLICIT syntax. For example, this allows you to convert some fields to attributes and others to elements. Refer to the SQL Server Books Online for more information. Unfortunately, the FOR XML query syntax is specific to SQL Server and isn’t supported by other database products.
To perform an XML query, you must use the SqlCommand.ExecuteXmlReader() method. This method returns an XmlReader with the results of your query as an XML fragment. You can move through the XmlReader one node at a time in a forward-only direction in the same way you use the XmlTextReader. In fact, XmlTextReader derives from XmlReader.
Here’s the code needed to retrieve and display a customer list on a web page:
//Define the command. string customerQuery =
"SELECT FirstName, LastName FROM Employees FOR XML AUTO, ELEMENTS"; SqlConnection con = new SqlConnection(connectionString);
SqlCommand com = new SqlCommand(customerQuery, con);
//Execute the command.
StringBuilder str = new StringBuilder(); try
{
con.Open();
XmlReader reader = com.ExecuteXmlReader();
470 C H A P T E R 1 2 ■ X M L
while (reader.Read())
{
// Process each employee.
if ((reader.Name == "Employees") && (reader.NodeType == XmlNodeType.Element))
{
reader.ReadStartElement("Employees");
str.Append(reader.ReadElementString("FirstName")); str.Append(" "); str.Append(reader.ReadElementString("LastName")); str.Append("<br />");
reader.ReadEndElement();
}
}
reader.Close();
}
finally
{
con.Close();
}
XmlText.Text = str.ToString();
Of course, life gets much more interesting when you combine an XML query with some of the other standards you’ve seen in this chapter, such as XPath searching or XSL transformation. These techniques aren’t for everyone, but they do give you the ability to transform your data into virtually any XML representation.
Summary
In this chapter, you got a taste of ASP.NET’s XML features. The class libraries for interacting with XML are available to any .NET application, whether it’s a Windows application, a web application, or a simple command-line tool. They provide one of the most fully featured toolkits for working with XML and other standards such as XPath, XML Schema, and XSLT.
XML is a vast topic, and there is much more to cover, such as advanced navigation, search and selection techniques, validation, and serialization. If you want to learn more about XML in .NET, you may want to refer to Pro .NET XML (Apress, 2005). But remember that you should use XML only where it’s warranted. XML is a great tool for persisting file-based data in a readable format and for sharing information with other application components and services. However, it doesn’t replace the core data management techniques you’ve seen in previous chapters.
472 C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
Working with the File System
The simplest level of file access just involves retrieving information about existing files and directories and performing typical file system operations such as copying files and creating directories. These tasks don’t involve actually opening or writing a file (both of which are tasks you’ll learn about later in this chapter).
The .NET Framework provides a few basic classes for retrieving file system information. They are all located in the System.IO namespace (and, incidentally, can be used in desktop applications in the same way they are used in web applications). They include the following:
•Directory and File: These classes provide static methods that allow you to retrieve information about any files and directories that are visible from your server.
•DriveInfo, DirectoryInfo, and FileInfo: These classes use similar instance methods and properties to retrieve the same information.
These two sets of classes provide similar methods and properties. The key difference is that you need to create a DirectoryInfo or FileInfo object before you can use any methods, whereas the static methods of the Directory and File classes are always available. Typically, the Directory and File classes are more convenient for one-off tasks. On the other hand, if you need to retrieve several pieces of information, it’s better to create DirectoryInfo and FileInfo objects. That way you don’t need to keep specifying the name of the directory or file each time you call a method. It’s also faster. That’s because the FileInfo and DirectoryInfo classes perform their security checks once—when you create the object instance. The Directory and File classes perform a security check every time you invoke a method.
The Directory and File Classes
The Directory and File classes provide a number of useful methods. Tables 13-1 and 13-2 tell the whole story. Note that every method takes the same parameter: a fully qualified path name identifying the directory or file you want the operation to act on.
Table 13-1. Directory Methods
Method |
Description |
CreateDirectory() |
Creates a new directory. If you specify a directory inside another |
|
nonexistent directory, ASP.NET will thoughtfully create all the |
|
required directories. |
Delete() |
Deletes the corresponding empty directory. To delete a directory |
|
along with its contents (subdirectories and files), add the optional |
|
second parameter of true. |
Exists() |
Returns true or false to indicate whether the specified directory |
|
exists. |
GetCreationTime(), |
Returns a DateTime object that represents the time the directory |
GetLastAccessTime(), |
was created, accessed, or written to. Each “Get” method has a |
and GetLastWriteTime() |
corresponding “Set” method, which isn’t shown in this table. |
GetDirectories(), GetFiles(), |
Returns an array of strings, one for each subdirectory, file, or drive |
and GetLogicalDrives() |
in the specified directory (depending on which method you’re |
|
using). This method can accept a second parameter that specifies |
|
a search expression (such as ASP*.*). Drive letters are in the format |
|
of c:\. |
C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S |
473 |
Method |
Description |
GetParent() |
Parses the supplied directory string and tells you what the parent |
|
directory is. You could do this on your own by searching for the \ |
|
character (or, more generically, the Path.DirectorySeparatorChar), |
|
but this function makes life a little easier. |
GetCurrentDirectory() and |
Allows you to set and retrieve the current directory, which is useful |
SetCurrentDirectory() |
if you need to use relative paths instead of full paths. Generally, |
|
you shouldn’t rely on these functions—use full paths instead. |
Move() |
Accepts two parameters: the source path and the destination path. |
|
The directory and all its contents can be moved to any path, as |
|
long as it’s located on the same drive. |
GetAccessControl() |
Returns a System.Security.AccessControl.DirectorySecurity object. |
|
You can use this object to examine the Windows access control |
|
lists (ACLs) that are applied on this directory and even change |
|
them programmatically. |
|
|
Table 13-2. File Methods |
|
|
|
Method |
Description |
Copy() |
Accepts two parameters: the fully qualified source filename and |
|
the fully qualified destination filename. To allow overwriting, use |
|
the version that takes a Boolean third parameter and set it to true. |
Delete() |
Deletes the specified file but doesn’t throw an exception if the file |
|
can’t be found. |
Exists() |
Indicates true or false whether a specified file exists. |
GetAttributes() and |
Retrieves or sets an enumerated value that can include any |
SetAttributes() |
combination of the values from the FileMode enumeration. |
GetCreationTime(), |
Returns a DateTime object that represents the time the file was |
GetLastAccessTime(), |
created, accessed, or last written to. Each “Get” method has a |
and GetLastWriteTime() |
corresponding “Set” method, which isn’t shown in this table. |
Move() |
Accepts two parameters: the fully qualified source filename and |
|
the fully qualified destination filename. You can move a file across |
|
drives and even rename it while you move it (or rename it without |
|
moving it). |
Create() and CreateText() |
Creates the specified file and returns a FileStream object that you |
|
can use to write to it. CreateText() performs the same task but |
|
returns a StreamWriter object that wraps the stream. |
Open(), OpenRead(), |
Opens a file (provided it exists). OpenText() and OpenWrite() |
OpenRead(), and |
open a file in read-only mode, returning a FileStream or |
OpenText() |
StreamReader. OpenWrite() opens a file in write-only mode, |
|
returning a FileStream. |
ReadAllText(), ReadLines(), |
Reads the entire file and returns its contents as a single string, an |
and ReadBytes() |
array of strings (one for each line), or an array of bytes. Use this |
|
method only for very small files. For larger files, use streams to |
|
read one chunk at a time and reduce the memory overhead. |
WriteAllText(), WriteLines(), |
Writes an entire file in one shot using a supplied string, array of |
and WriteBytes() |
strings (one for each line), or array of bytes. If the file already |
|
exists, it is overwritten. |
GetAccessControl() |
Returns a System.Security.AccessControl.DirectorySecurity |
|
object. You can use this object to examine the Windows ACLs |
|
that are applied on this directory and even change them |
|
programmatically. |
|
|
474 C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
■Tip The only feature that the File class lacks (and the FileInfo class provides) is the ability to retrieve the size of a specified file.
The File and Directory methods are completely intuitive. For example, you could use this code to write a dynamic list displaying the name of each file in the current directory:
string directoryName = @"c:\Temp";
// Retrieve the list of files, and display it in the page. string[] fileList = Directory.GetFiles(ftpDirectory); foreach (string file in fileList)
{
lstFiles.Items.Add(file);
}
In this example, the string with the file path c:\Temp is preceded by an at (@) character. This tells C# to interpret the string exactly as written. Without this character, C# would assume the directory separation character (\) indicates the start of a special character sequence. Another option is to use the escaped character sequence \\, which C# reads as a single literal slash. In this case, you would write the path as c:\\Temp.
Because the list of files is simply an ordinary list of strings, it can easily be bound to a list control, resulting in the following more efficient syntax for displaying the files on a page:
string directoryName = @"c:\Temp";
lstFiles.DataSource = Directory.GetFiles(ftpDirectory); lstFiles.DataBind();
■Note For this code to work, the account that is used to run the ASP.NET worker process must have rights to the directory you’re using. Otherwise, a SecurityException will be thrown when your web page attempts to access the file system. You can modify the permissions for a directory by right-clicking the directory, selecting Properties, and choosing the Security tab. If you are using the default ASP.NET settings with IIS 5, you need to grant read and write permissions to the ASPNET account. (With IIS 6, the local network account is used instead.) Alternatively, you might find it easier to modify the account that ASP.NET uses so you don’t need to change these permissions at all. For more information, refer to Chapter 18, which explains how to configure the account used for ASP.NET applications.
The DirectoryInfo and FileInfo Classes
The DirectoryInfo and FileInfo classes mirror the functionality in the Directory and File classes. In addition, they make it easy to walk through directory and file relationships. For example, you can easily retrieve the FileInfo objects of files in a directory represented by a DirectoryInfo object.
Note that while the Directory and File classes expose only methods, DirectoryInfo and FileInfo provide a combination of properties and methods. For example, while the File class had separate GetAttributes() and SetAttributes() methods, the FileInfo class exposes a read-write Attributes property.
C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S |
475 |
Another nice thing about the DirectoryInfo and FileInfo classes is that they share a common set of properties and methods because they derive from the common FileSystemInfo base class. Table 13-3 describes the members they have in common.
Table 13-3. DirectoryInfo and FileInfo Members
Member |
Description |
Attributes |
Allows you to retrieve or set attributes using a combination of values |
|
from the FileAttributes enumeration. |
CreationTime, |
Allows you to set or retrieve the creation time, last access time, |
LastAccessTime, |
and last write time using a DateTime object. |
and LastWriteTime |
|
Exists |
Returns true or false depending on whether the file or directory exists. |
|
In other words, you can create FileInfo and DirectoryInfo objects that |
|
don’t actually correspond to current physical directories, although you |
|
obviously won’t be able to use properties such as CreationTime and |
|
methods such as MoveTo(). |
FullName, Name, and |
Returns a string that represents the fully qualified name, the directory |
Extension |
or filename (with extension), or the extension on its own, depending on |
|
which property you use. |
Delete() |
Removes the file or directory, if it exists. When deleting a directory, it |
|
must be empty, or you must specify an optional parameter set to true. |
Refresh() |
Updates the object so it’s synchronized with any file system changes |
|
that have happened in the meantime (for example, if an attribute was |
|
changed manually using Windows Explorer). |
Create() |
Creates the specified directory or file. |
MoveTo() |
Copies the directory and its contents or the file. For a DirectoryInfo |
|
object, you need to specify the new path; for a FileInfo object, you |
|
specify a path and filename. |
|
|
In addition, the FileInfo and DirectoryInfo classes have a couple of unique members, as indicated in Tables 13-4 and 13-5.
Table 13-4. Unique DirectoryInfo Members
Member |
Description |
Parent and Root |
Returns a DirectoryInfo object that represents the parent or root |
|
directory. |
CreateSubdirectory() |
Creates a directory with the specified name in the directory |
|
represented by the DirectoryInfo object. It also returns a new |
|
DirectoryInfo object that represents the subdirectory. |
GetDirectories() |
Returns an array of DirectoryInfo objects that represent all the |
|
subdirectories contained in this directory. |
GetFiles() |
Returns an array of FileInfo objects that represent all the files contained |
|
in this directory. |
|
|
476 C H A P T E R 1 3 ■ F I L E S A N D S T R E A M S
Table 13-5. Unique FileInfo Members
Member |
Description |
Directory |
Returns a DirectoryInfo object that represents the parent directory. |
DirectoryName |
Returns a string that identifies the name of the parent directory. |
Length |
Returns a long (64-bit integer) with the file size in bytes. |
CopyTo() |
Copies a file to the new path and filename specified as a parameter. It |
|
also returns a new FileInfo object that represents the new (copied) file. |
|
You can supply an optional additional parameter of true to allow |
|
overwriting. |
Create() and |
Creates the specified file and returns a FileStream object that you can |
CreateText() |
use to write to it. CreateText() performs the same task but returns a |
|
StreamWriter object that wraps the stream. |
Open(), OpenRead(), |
Opens a file (provided it exists). OpenRead() and OpenText() open a |
OpenText(), and |
file in read-only mode, returning a FileStream or StreamReader. |
OpenWrite() |
OpenWrite() opens a file in write-only mode, returning a FileStream. |
|
|
When you create a DirectoryInfo or FileInfo object, you specify the full path in the constructor, as shown here:
DirectoryInfo myDirectory = new DirectoryInfo(@"c:\Temp");
FileInfo myFile = new FileInfo(@"c:\Temp\readme.txt");
When you create a new DirectoryInfo or FileInfo object, you’ll receive an exception if the path you used isn’t properly formed (for example, if it contains illegal characters). However, the path doesn’t need to correspond to a real physical file or directory. If you’re not sure, you can use Exists to check whether your directory or file really exists.
If the file or directory doesn’t exist, you can always use a method such as Create() to create it. Here’s an example:
// Define the new directory and file.
DirectoryInfo myDirectory = new DirectoryInfo(@"c:\Temp\Test"); FileInfo myFile = new FileInfo(@"c:\Temp\Test\readme.txt");
//Now create them. Order here is important.
//You can't create a file in a directory that doesn't exist yet. myDirectory.Create();
FileStream stream = myFile.Create(); stream.Close();
The FileInfo and DirectoryInfo objects retrieve information from the file system the first time you query a property. They don’t check for new information on subsequent use. This could lead to inconsistency if the file changes in the meantime. If you know or suspect that file system information has changed for the given object, you should call the Refresh() method to retrieve the latest information.
The DirectoryInfo class doesn’t provide any property for determining the total size information. However, you can calculate the size of all the files in a particular directory quite easily by totaling the FileInfo.Length contribution of each one.
Before you take this step, you need to decide whether to include subdirectories in the total. The following method lets you use either approach:
