Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
107
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

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.

C H A P T E R 1 3

■ ■ ■

Files and Streams

Most web applications rely heavily on databases to store information. Databases are unmatched in multiuser scenarios. They handle simultaneous access without a hitch, and they support caching and low-level disk optimizations that guarantee blistering performance. Quite simply, an RDBMS offers the most robust and best-performing storage for data.

Of course, most web developers inevitably face a scenario where they need to access data in other locations, such as the file system. Common examples include reading information produced by another application, writing a quick-and-dirty log for testing purposes, and creating a management page that allows administrators to upload files and view what’s currently on the server. In this chapter, you’ll learn how to use the classes in the System.IO namespace to get file system information, work with file paths as strings, write and read files, and serialize objects.

FILE ACCESS CHANGES IN .NET 2.0

The classes for retrieving file information and for reading and writing streams remain essentially the same in

.NET 2.0, with a few useful additions. Here are the changes you’ll see in this chapter, in their order of appearance:

The DriveInfo class: Along with the existing DirectoryInfo and FileInfo classes, the DriveInfo class allows you to get information about a logical drive on the current computer. It’s primarily useful for determining free and used space.

The FileUpload control: FileUpload works almost the same as the HtmlInput control, allowing a user to upload files from a browser. It has one minor convenience—it sets the encoding type of the <form> tag automatically, dodging a common mistake.

Compression: The new System.IO.Compression namespace provides classes that let you compress data using the industry-standard GZIP algorithm.

471

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:

C H A P T E R 1 3 F I L E S A N D S T R E A M S

477

private static long GetDirectorySize(DirectoryInfo directory, bool includeSubdirectories)

{

long totalSize = 0;

// Add up each file.

FileInfo[] files = directory.GetFiles(); foreach (FileInfo file in files)

{

totalSize += file.Length;

}

// Add up each subdirectory, if required. if (includeSubdirectories)

{

DirectoryInfo[] dirs = directory.GetDirectories(); foreach (DirectoryInfo dir in dirs)

{

totalSize += CalculateDirectorySize(dir, true);

}

}

return totalSize;

}

For information about free space, you need to use the DriveInfo class.

The DriveInfo Class

The DriveInfo class (new in .NET 2.0) allows you to retrieve information about a drive on your computer. Few pieces of information will interest you—typically, the DriveInfo class is just used to retrieve the total amount of used and free space.

Table 13-6 shows the DriveInfo members. Unlike the FileInfo and DriveInfo classes, there is no Drive class to provide instance versions of these methods.

Table 13-6. DriveInfo Members

Member

Description

TotalSize

Gets the total size of the drive, in bytes. This includes allocated and free

 

space.

TotalFreeSpace

Gets the total amount of free space, in bytes.

AvailableFreeSpace

Gets the total amount of available free space, in bytes. Available space may

 

be less than the total free space if you’ve applied disk quotas limiting the

 

space that the ASP.NET process can use.

DriveFormat

Returns the name of the file system used on the drive (such as NTFS or

 

FAT32).

DriveType

Returns a value from the DriveType enumeration, which indicates

 

whether the drive is a fixed, network, CD-ROM, RAM, or removable

 

drive. (It returns Unknown if the drive’s type cannot be determined.)

IsReady

Returns whether the drive is ready for reading or writing operations.

 

Removable drives are considered “not ready” if they don’t have any media.

 

For example, if there’s no CD in a CD drive, IsReady will return false. In this

 

situation, it’s not safe to query the other DriveInfo properties. Fixed drives

 

are always read.

Continued