Exploring the System.IO namespace(2)

来源:岁月联盟 编辑:zhu 时间:2003-07-12
MemoryStream
As we already saw in the diagram, this class derives from the Stream base class and it is primarily used to read and write bytes of data from memory. The amount of memory buffer allocated for storing the data is determined by the value we pass to one of the many overloaded constructors of this class. The default size is 256 bytes.
The Capacity property allows us to determine the amount of data the memory stream can hold, after which it will have to do reallocation of its buffer. Since it is a read/write property, we can also set this property to shrink the size of the memory stream to the desired size. To determine the amount of data that is currently held in the memory stream, we can use the read-only Length property.
Let us consider a simple example to see the MemoryStream class in action:
private void Page_Load(object sender, System.EventArgs e)
{
   // Put user code to initialize the page here
   MemoryStream memStream = new MemoryStream();
   StreamWriter writer = new StreamWriter(memStream);
   writer.WriteLine("This line is written to the memory stream");
   writer.Flush();
   Byte[] arrayByte;
   //convert the stream into a byte array
   arrayByte = memStream.ToArray();
   Response.Write ("Capacity = " + memStream.Capacity.ToString() +  "<br>");
   Response.Write ("Lengh = " + memStream.Length.ToString() +  "<br>");
   Response.Write ("CanRead = " + memStream.CanRead.ToString()+  "<br>");
   Response.Write ("CanSeek = " + memStream.CanSeek.ToString()+  "<br>");
   Response.Write ("CanWrite = " + memStream.CanWrite.ToString()+  "<br>");
   writer.Close();
}
In the above code, once we create an instance of the MemoryStream class, we associate that with the StreamWriter class by passing it to the constructor of the StreamWriter class. Then we write a line of text using the WriteLine method of the StreamWriter class.
To convert the memory stream into a byte array, we use the ToArray method. Once we have the data in the form of byte array, it can then be easily sent across the wire to another application which might want to process the data.
   arrayByte = memStream.ToArray();
In the following statements, we determine the capabilities of the MemoryStream class by inspecting its properties:
Response.Write ("CanRead = " + memStream.CanRead.ToString()+  "<br>");
Response.Write ("CanSeek = " + memStream.CanSeek.ToString()+  "<br>");
Response.Write ("CanWrite = " + memStream.CanWrite.ToString()+  "<br>");
If you execute the above code, the generated output looks like the following:

StringReader and StringWriter
The StringReader class derives from the TextReader class, and it uses a string as the input stream. At the time of instantiating the StringReader class, we can pass the string to be read as an argument to its constructor. Like StringReader, the StringWriter class derives from the TextWriter class. The StringWriter class uses a StringBuilder object internally to build and manage the output stream.
The following snippet of code shows how to use the StringWriter class to manipulate the string output stream. Once we are done writing the contents into the underlying string, we can then retrieve its contents using any one of the following ways:
  • By calling the ToString method
  • By invoking the GetStringBuilder method - this method returns the actual StringBuilder object that is used to construct the underlying stream.

private void Page_Load(object sender, System.EventArgs e)
{
   // Put user code to initialize the page here
   StringWriter writer = new StringWriter();
   writer.WriteLine("This is the first line");
   writer.WriteLine("This is the second line");
   writer.WriteLine("This is the third line");
   //Get the Contents by invoking through the ToString method
   //txtContents.Text = writer.ToString();
   //Can also get the Contents through the underlying StringBuilder object
   txtContents.Text = writer.GetStringBuilder().ToString();
   writer.Flush();
   writer.Close();
}
The output produced by the above code looks like the following:

FileStream
The FileStream class is intended for reading and writing binary data to any binary file. However if you wish, you could also use this to read and write to any file. One of the cool features of the FileStream class is its ability to support random access of files. This makes the FileStream class the most preferred class when it comes to reading data from a file. This is especially true when designing an application that often needs to perform random accessing of files. The random accessing of files is made possible through the use of the Seek method. The FileStream class supports both synchronous and asynchronous read and write operations, with the default being the synchronous mode.
In order to construct a FileStream object, we need to provide any of the following four pieces of information:
  • Path - This is used to specify the path of the file we are trying to access.
  • FileMode - This parameter allows us to specify the mode in which we want to open the file. For example, if we want to open a file for appending, we can pass the enum constant FileMode.Append. The other valid values for this argument include: FileMode.Create, FileMode.CreateNew, FileMode.Open, FileMode.OpenOrCreate, and FileMode.Truncate.
  • FileAccess - Allows us to detail the kind of access we require on the file. Valid values for this argument include: FileAccess.Read, FileAccess.Write, and FileAccess.ReadWrite.
  • FileShare - Allows us to control the kind of access others will have when you are working on the file. It can have any one of the following values: FileShare.None, FileShare.Read, FileShare.ReadWrite, and FileShare.Write.

For detailed information on all the properties and methods of the FileStream class, please take a look at the following link: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemIODirectoryInfoClassTopic.asp.
To help realize the implementation and usage of the FileStream class, we will implement an error handling solution for our application by using the FileStream class. Whenever any exceptions occur in the system, we will capture them in the Application_Error event and process them by logging them into a text file. We will use the FileStream class to perform the logging of exception information into a text file.
The following lines of code are required to implement this:
protected void Application_Error(Object sender, EventArgs e)
{
   string fileName = Server.MapPath(@"/IOExamples/ErrorLog/")+
                     System.Guid.NewGuid().ToString()  + ".txt";
   FileStream fileStream = new FileStream(fileName,FileMode.Append,
                              FileAccess.Write,FileShare.ReadWrite);
   StreamWriter writer = new StreamWriter(fileStream);
   writer.Write(Server.GetLastError().ToString());
   writer.Flush();
   writer.Close();
}
We start our implemenation by constructing the full path of the file:
   string fileName = Server.MapPath(@"/IOExamples/ErrorLog/")+
                     System.Guid.NewGuid().ToString()  + ".txt";
In the above line, we use the NewGuid method of the System.Guid class to create a new unique guid. This means that every time an exception occurs, a new file will be created in the ErrorLog directory. We will discuss the need for this when we discuss the FileSystemWatcher in the latter part of this article.
We then create an instance of the FileStream class and specify that we want to open the file for appending.
   FileStream fileStream = new FileStream(fileName,FileMode.Append,
                              FileAccess.Write,FileShare.ReadWrite);
Once we create an instance of the FileStream class, we can associate it with a StreamWriter by passing it to the constructor:
   StreamWriter writer = new StreamWriter(fileStream);
Here, we write the exception information using the Write method of the StreamWriter object.
   writer.Write(Server.GetLastError().ToString());
Finally, we flush and close the writer:
   writer.Flush();
   writer.Close();


In the above steps, the following sequence of operations are executed:
  • In the Application_Start event, set up the FileSystemWatcher component to monitor the ErrorLog directory for creation of new files. While setting up the FileSystemWatcher component, we also specify the method to be called (or notified) whenever a new file is created in the ErrorLog directory. In our application, we will use a method called OnCreated for this purpose.
  • In the Application_Error event, as explained already in the explanation of the FileStream class, we have code to process the exceptions by creating a new file in the directory ErrorLog.

Now that we have set up the above-mentioned procedures, let us see what happens at runtime. Whenever an exception occurs, the control is transferred to the Application_Error event, where the exception is logged in to a newly created file in the ErrorLog directory.
For example, if we deliberately generate a System.DivideByZeroException by dividing a number by zero in the code, our application handles the exception by generating a log. As soon as a new file is created in the ErrorLog directory, the FileSystemWatcher picks it up and raises an event (or notification) to our application by invoking the OnCreated method (which we have already specified at the time of setting up the file monitoring in the Application_Start event). In the OnCreated method, we read the contents of the file and send an Email to the administrator informing him of the error.
Let us look at the code required to implement this.
The meat of this example is the Application_Start event as that is where we set up the file monitoring on the ErrorLog directory:
protected void Application_Start(Object sender, EventArgs e)
{
   FileSystemWatcher watcher = new FileSystemWatcher();
   //Set the path to specify the directory to monitored
   watcher.Path = Server.MapPath(@"/IOExamples/ErrorLog/");
   //Monitor for files that end with extension .txt
   watcher.Filter = "*.txt";
   //Indicate not to monitor the subdirectories
   watcher.IncludeSubdirectories = false;
   //Enable the component to begin watching for changes
   watcher.EnableRaisingEvents = true;
   watcher.Created += new FileSystemEventHandler(OnCreated);
}
We start by creating an instance of the FileSystemWatcher component.
   FileSystemWatcher watcher = new FileSystemWatcher();
Once we create the FileSystemWatcher component, we then set the Path property to the physical path returned by the Server.MapPath method:
   //Set the path to specify the directory to be monitored
   watcher.Path = Server.MapPath(@"/IOExamples/ErrorLog/");
Then we specify that we want to monitor only for text files by using the following line of code:
   //Monitor for files that end with extension .txt
   watcher.Filter = "*.txt";
We also specify that we do not want to monitor the subdirectories by setting the IncludeSubDirectories to false:
   //Indicate not to monitor the subdirectories
   watcher.IncludeSubdirectories = false;
Here we start the monitoring process by setting the EnableRaisingEvents to true:
   //Enable the component to begin watching for changes
   watcher.EnableRaisingEvents = true;
We then hook the Created event up to the OnCreated method, which gets called whenever a new file is created:
   watcher.Created += new FileSystemEventHandler(OnCreated);
As mentioned already, the OnCreated method is called whenever a new file is created. In this method, we read the contents of the newly created file and send an Email to the administrator informing him that the exception that has occurred:
protected void OnCreated(object source,FileSystemEventArgs e)
{
   String line;
   FileStream fileStream = new FileStream(e.FullPath,FileMode.Open,
                                                   FileAccess.Read);
   StreamReader  reader  = new StreamReader(fileStream);
   StringBuilder builder = new StringBuilder();
   //Read the contents of the file and append it to the StringBuilder
   while(reader.Peek() != -1)
   {
      line = reader.ReadLine();
      builder.Append(line);
   }

   //Set the Message properties
   MailMessage message = new MailMessage();
   message.From = "IOExamples@admin.com";
   message.To = "webadmin@admin.com";
   message.Subject = "An error has occured in your site";
   message.Body = builder.ToString();
   //Send EMail
   SmtpMail.Send(message);
}
We start by creating an instance of the FileSteam object, passing in appropriate arguments. The FullPath property of the FileSystemEventArgs object returns the full path of the newly created file. To the constructor of the FileStream class, we pass this as an argument to the Path parameter. We also specify that we want to open the file for reading by using the enum constants FileMode.Open and FileAccess.Read:
   FileStream fileStream = new FileStream(e.FullPath,
                     FileMode.Open,FileAccess.Read);
We then loop through the contents of the file by using the combination of the Peek and ReadLine methods and append it to the string builder object:
   while(reader.Peek() != -1)
   {
      line = reader.ReadLine();
      builder.Append(line);
   }
In the following lines of code, we set the properties of the MailMessage object to the required values:
   //Set the Message properties
   MailMessage message = new MailMessage();
   message.From = "IOExamples@admin.com";
   message.To = "webadmin@admin.com";
   message.Subject = "An error has occured in your site";
   message.Body = builder.ToString();
Finally, we send the mail using the static method Send of the SmtpMail class.
   //Send EMail
   SmtpMail.Send(message);