[ Team LiB ] |
3.1 Writing DataAs with XmlReader, I'll start by taking a general look at how data is written in .NET. I've already covered input, and output is very similar in that most operations involve the Stream class. After a general introduction to how the writing process works, I'll show you a quick and simple way of writing text to a file. 3.1.1 Filesystem I/OI covered the basics of opening and reading a file through the File and FileInfo objects in Chapter 2. In this section, I'll focus on writing to a file using the same objects. To begin with, File has a Create( ) method. This method takes a filename as a parameter and returns a FileStream, so the most basic creation and writing to a file is fairly intuitive. Stream and its subclasses implement a variety of Write( ) methods, including one that writes an array of bytes to the Stream. The following code snippet creates a file named myfile.txt and writes the text .NET & XML to it: byte [ ] buffer = new byte [ ] {46,78,69,84,32,38,32,88,77,76}; string filename = "myfile.txt"; FileStream stream; stream = File.Create(filename); stream.Write(buffer,0,buffer.Length);
If the file already exists, the previous code overwrites the files's current contents. You may not want to do that in practice; you may prefer to append to the file if it already exists. You can handle this very easily in .NET in several different ways. This snippet shows one way, with the changes highlighted: byte [ ] buffer = new byte [ ] {46,78,69,84,32,38,32,88,77,76}; string filename = "myfile.txt"; FileStream stream; if (File.Exists(filename)) { // it already exists, let's append to it stream = File.OpenWrite(filename); stream.Seek(0,SeekOrigin.End); } else { // it does not exist, let's create it stream = File.Create(filename); } stream.Write(buffer,0,buffer.Length); SeekOrigin is an enumeration in the System.IO namespace that indicates where the Seek( ) method should seek from. In this code, I'm seeking 0 bytes from the end, but you could also seek from the beginning of the Stream (SeekOrigin.Begin) or from the current position (SeekOrigin.Current). 3.1.1.1 File access and permissionsThere are several other ways to open a file for writing. For example, this snippet shows several changes from the previous one. The changes are highlighted: byte [ ] buffer = new byte [ ] {46,78,69,84,32,38,32,88,77,76}; string filename = "myfile.txt"; FileStream stream; FileMode fileMode; if (File.Exists(filename)) { // it already exists, let's append to it fileMode = FileMode.Append; } else { // it does not exist, let's create it fileMode = FileMode.CreateNew; } stream = File.Open(filename,fileMode,FileAccess.Write,FileShare.None); stream.Write(buffer,0,buffer.Length); The File.Open( ) method has several overloads with additional parameters. The FileMode enumeration specifies what operations the file is to be opened for. Table 3-1 lists the FileMode enumerations.
The FileAccess enumeration restricts the operations a program can exercise on the file, once it has been opened. Table 3-2 details the FileAccess enumerations.
FileShare restricts what operations other applications can exercise. Table 3-3 describes the FileShare enumerations.
3.1.1.2 Encodings and StreamWriterHaving to create an array of bytes to write with the Stream.Write( ) method is a bit tiresome. Luckily, there are at least two ways to work around this. The first is System.Text.Encoding. This class contains methods to convert strings to and from byte arrays, for a given number of standard encodings, including ASCII, UTF-8, and UTF-16. These encodings are provided as static properties of the Encoding class. Strings in .NET are stored in Unicode—while ASCII characters are each stored in a single byte, Unicode characters are stored in four bytes. The GetBytes( ) method takes a .NET string and returns an array of bytes in the appropriate encoding, suitable for use by Stream.Write( ):
string message = "Hello, world."; byte [ ] buffer = Encoding.ASCII.GetBytes(message); string filename = "myfile.txt"; FileStream stream; FileMode fileMode; if (File.Exists(filename)) { // it already exists, let's append to it fileMode = FileMode.Append; } else { // it does not exist, let's create it fileMode = FileMode.CreateNew; } stream = File.Open(filename,fileMode,FileAccess.Write,FileShare.None); stream.Write(buffer,0,buffer.Length); Just to belabor the point, remember that a C# byte is the familiar eight-bit byte, but a C# char is a Unicode character. The encodings defined in the .NET Framework are shown in Table 3-4.
In addition, any other encodings are accessible by calling Encoding.GetEncoding( ) and passing the code page or encoding name. The other way to write a stream of characters is even simpler. A FileStream is a subclass of Stream, which can be used as a parameter to the StreamWriter's constructor. StreamWriter is analogous to StreamReader, and is a subclass of TextWriter, which is optimized to write textual data to a Stream. The TextWriter's Write( ) and WriteLine( ) methods take care of the encoding of various datatypes when writing to a Stream: string textToWrite = "This is the text I want to write to the file."; string filename = "myfile.txt"; FileStream stream; FileMode fileMode; if (File.Exists(filename)) { // it already exists, let's append to it fileMode = FileMode.Append; } else { // it does not exist, let's create it fileMode = FileMode.CreateNew; } stream = File.Open(filename,fileMode,FileAccess.Write,FileShare.None); StreamWriter writer = new StreamWriter(stream); writer.Write(textToWrite); writer.Flush( ); writer.Close( ); The last two lines of this code snippet cause the output buffer to be flushed, and the file to be closed. Every time you write to the file and you want the file on disk to reflect the changes immediately, it is important to call Flush( ) on the Stream or StreamWriter. You can also indicate that the contents of the file are to be flushed to disk automatically by setting AutoFlush to true: StreamWriter writer = new StreamWriter(stream);
writer.AutoFlush = true;
writer.Write(textToWrite);
When you are completely done with a file, you should call Close( ) on the File or the Stream. If you don't call Close( ) yourself, the file will be closed when the garbage collector cleans up the method's local variables. Unfortunately, you don't know when that will happen, so it's always best to close files yourself.
There's an even quicker way to append to an existing file. The StreamWriter class has a constructor that takes a filename as a parameter. Since StreamWriter inherits from TextWriter, it implements the IDisposable interface, which allows you to use the using keyword to automatically close the Stream at the end of the using block. All the code you wrote above could instead be simplified to five lines, if all you want to do is write a quick chunk of text to a file: string textToWrite = "This is the text I want to write to the file."; string filename = "myfile.txt"; using (StreamWriter writer = new StreamWriter(filename,true)) { writer.Write(textToWrite); } The second parameter to the StreamWriter constructor indicates that the text is to be appended to the file if the file already exists. 3.1.2 Network I/OJust as with input, network output can use Socket, Stream, or WebRequest objects. The basic unit of network communication is the Socket. For higher-level network output, you can use the WebRequest class. Whether communicating over a Socket or a WebRequest, however, you'll be using a Stream to actually read and write data. 3.1.2.1 Writing data with SocketsTo communicate over a network using a Socket, there must be a server of some sort listening for requests at the other end. The construction of network application servers is beyond the scope of this book, but Example 3-1 shows you how to create a simple network client program. Example 3-1. A simple network client programusing System; using System.IO; using System.Net.Sockets; public class NetWriter { public static void Main(string [ ] args) { string address = "example.com"; int port = 9999; TcpClient client = new TcpClient(address,port); NetworkStream stream = client.GetStream( ); StreamWriter writer = new StreamWriter(stream); writer.WriteLine("hello\r\n"); writer.Flush( ); using (StreamReader reader = new StreamReader(stream)) { while (reader.Peek( ) != -1) { Console.WriteLine(reader.ReadLine( )); } } } } The Main( ) method can be broken down into its major steps. The first step is to initialize some variables: string address = "example.com"; int port = 9999; TcpClient is a convenient specialization of a TCP/IP client Socket. The GetStream( ) method makes the connection and returns a Stream to communicate with the remote Socket: TcpClient client = new TcpClient(address,port); NetworkStream stream = client.GetStream( ); Next, you use a StreamWriter to write a single line directly to the remote Socket. Since you've connected to port 9999 of the server example.com, you can write the line hello, followed by an end-of-line marker, to the Socket, and receive back the data that the server wants to send. Once again, it's important to call Flush( ), so that the data is actually written to the Stream:
StreamWriter writer = new StreamWriter(stream); writer.WriteLine("hello\r\n"); writer.Flush( ); Now that you have written data to the Stream, you can read any data that the server sends back. This while loop checks that there is more data to read, and then echoes it to the console. Finally, the using statement automatically closes the Stream, which closes the underlying network Socket and releases any resources the Socket is holding, much like closing a FileStream: using (StreamReader reader = new StreamReader(stream)) { while (reader.Peek( ) != -1) { Console.WriteLine(reader.ReadLine( )); } } Similar to the way the FileStream class owns its underlying file handle, the StreamWriter owns the underlying network stream. In Socket-based communication, you shouldn't close the StreamWriter until you're done with the entire conversation, because the same underlying Stream will be used to read the response. The Stream represents both sides of the conversation. 3.1.2.2 Writing data with WebRequestAs I mentioned in Chapter 2, the WebRequest class supports the http, https, and file URL schemes, so you could theoretically use a WebRequest to send data to a web server. However, the mechanism for writing files to a web server is not so clear cut; the options for writing data to URLs are limited to the methods that HTTP supports, namely GET, HEAD, POST, PUT, DELETE, TRACE, and OPTIONS. In Chapter 2, I used the default HTTP method, GET. Table 3-5 describes the other HTTP methods.
There are at least two ways to write data to a web server. The first involves complicated URLs using the POST or GET methods and CGI or ASP.NET code on the server, all of which are outside the scope of this book. The second requires the server to support the HTTP PUT method, which may also require some custom setup on the server. Example 3-2 shows a simple program that constructs a WebRequest using the PUT method. Example 3-2. Program to send an HTTP PUT requestusing System; using System.IO; using System.Net; public class HttpPut { public static void Main(string [ ] args) { string url = "http://myserver.com/file.txt"; string username = "niel"; string password = "secret"; string data = "This data should be written to the URL."; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "PUT"; request.Credentials = new NetworkCredential(username, password); request.ContentLength = data.Length; request.ContentType = "text/plain"; using (StreamWriter writer = new StreamWriter(request.GetRequestStream( ))) { writer.WriteLine(data); } WebResponse response = request.GetResponse( ); using (StreamReader reader = new StreamReader(response.GetResponseStream( ))) { while (reader.Peek( ) != -1) { Console.WriteLine(reader.ReadLine( )); } } } }
Let's look at this code in small pieces: HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "PUT"; request.ContentLength = data.Length; request.ContentType = "text/plain"; request.Credentials = new NetworkCredential(username, password); This code creates a new WebRequest to communicate with the server and sets the method to PUT. With a PUT request, you need to set the ContentLength property before writing to the Stream. If the PUT method is properly implemented, it should also require some sort of authentication to prevent improper access; that's what the NetworkCredential is for:
using (StreamWriter writer = new StreamWriter(request.GetRequestStream( ))) { writer.WriteLine(data); } This code writes the content of the file directly to the WebRequest's Stream. It's important to close the Stream (done here by virtue of the IDisposable interface and the using statement) to release the connection for reuse; otherwise, the application will run out of connections:
WebResponse response = request.GetResponse( ); using (StreamReader reader = new StreamReader(response.GetResponseStream( ))) { while (reader.Peek( ) != -1) { Console.WriteLine(reader.ReadLine( )); } } Finally, data is read from the WebResponse. Typically, the data returned from a PUT request should include the URL of the file you created or updated. Once you have a WebRequest, you can also set its WebProxy as I demonstrated in Chapter 2. There is a whole world of detail to be examined when it comes to HTTP PUT. However, I just wanted to give you a taste of writing data across the network, because a Stream is a Stream as far as XmlWriter is concerned, and a local file may just as well be halfway across the planet. |
[ Team LiB ] |