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

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

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

702 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

Figure 20-23. Examining the current user’s storage using storeadm.exe

The Type of System.IO.IsolatedStorage

Before examining how to write data into (and read data from) isolated storage, consider Table 20-14, which documents the core types of isolated storage. As you can see, this namespace is refreshingly small, given that the types are used in conjunction with the basic types of System.IO.

Table 20-14. The Types of System.IO.IsolatedStorage

System.IO.IsolatedStorage Type

Meaning in Life

IsolatedStorage

This type represents the abstract base class from which all

 

isolated storage implementations must derive.

IsolatedStorageScope

This enum controls the level of isolation to make use of

 

(assembly, application domain, roaming).

IsolatedStorageException

This type specifies the exception that is thrown when an

 

operation in isolated storage fails.

IsolatedStorageFile

This type represents an isolated storage area containing files and

 

directories.

IsolatedStorageFileStream

This type exposes a file within isolated storage.

 

 

Obtaining a Store Using IsolatedStorageFile

When you wish to store application data in isolated storage, the first step is to decide the level of isolation you which to establish. Recall that storage is always isolated by the current user; however, a store can also be established using assembly or application domain evidence (as well as via roaming profiles, which we will not examine here). To configure the correct isolation level, one option is

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

703

to establish values using the IsolatedStorageScope enumeration and call IsolatedStorageFile. GetStore(). As a shorthand notation, however, you can call the static GetUserStoreForDomain() or

GetUserStoreForAssembly() of the IsolatedStorageFile type. Consider the following examples:

static void GetAppDomainStorageForUser()

{

//Open up isolated storage based on identity of

//assembly and AppDomain (short hand).

IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForDomain();

//Or combine flags and use GetStore().

IsolatedStorageFile store2 = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Domain, null, null);

}

static void GetAssemblyStorageForUser()

{

//Open up isolated storage based on identity of

//assembly (short hand).

IsolatedStorageFile store2 =

IsolatedStorageFile.GetUserStoreForAssembly();

// Or combine flags and use GetStore().

IsolatedStorageFile store2 = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);

}

Notice that regardless of the approach taken, the end result is you receive an IsolatedStorageFile object. Using this type, you are able to write data into the store, read data from a store, and create a custom directory structure within the current user’s store. Table 20-15 documents some of the interesting members of IsolatedStorageFile.

Table 20-15. Members of IsolatedStorageFile

Member

Meaning in Life

CurrentSize, MaximumSize

These read-only properties allow you to view size characteristics of

 

isolated storage.

Scope

This property shows the scope of isolation (user, assembly,

 

AppDomain).

CreateDirectory()

This method creates a new directory in the store.

DeleteDirectory()

This method deletes a directory from the store.

DeleteFile()

This method deletes a file within a given directory.

GetDirectoryNames()

This method allows you to iterate over named directories.

GetEnumerator()

This method gets a scope-specific IEnumerator.

GetFiles()

This method gets files within a specific store.

GetStore()

This overloaded method obtains isolated storage corresponding to

 

the given application domain and assembly evidence objects and

 

isolated storage scope.

Continued

704 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

Table 20-15. Continued

Member

Meaning in Life

GetUserStoreForAssembly() This method obtains isolated storage corresponding to the calling code’s assembly identity.

GetUserStoreForDomain() This method obtains isolated storage corresponding to the application domain identity and assembly identity.

Remove()

This method removes stores.

Writing Data to Storage

Once you have obtained a store, your next task is to create an instance of the IsolatedStorageFileStream type, which represents the file in the store you will be using to persist your data. Like other IO streams, this type can be configured using the System.IO.FileMode enumeration examined earlier in this chapter. To illustrate, create a new Console Application project named SimpleIsoStorage and be sure you import the System.IO and System.IO.IsolatedStorage namespaces. Now, update Main() to call the following helper method of the Program type:

static void WriteTextToIsoStorage()

{

//Open up isolated storage based on identity of

//user + assembly evidence.

using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly())

{

// Now create an IsolatedStorageFileStream type. using (IsolatedStorageFileStream isStream

=new IsolatedStorageFileStream("MyData.txt", FileMode.OpenOrCreate, store))

{

//Layer this stream into a StreamWriter

//and write out some text.

using (StreamWriter sw = new StreamWriter(isStream))

{

sw.WriteLine("This is my data."); sw.WriteLine("Cool, huh?");

}

}

}

}

Here, we begin by obtaining a store for the user based on the identity of the executing assembly (SimpleIsoStorage.exe) by calling IsolatedStorageFile.GetUserStoreForAssembly(). Next, we create a new IsolatedStorageFileStream object, specifying a new file to be named MyData.txt that will be created (or opened if it currently exists) in the store we just obtained. Finally, we layer the

IsolatedStorageFileStream object into a System.IO.StreamWriter and pump out a few lines of text. If you execute this application, you will then be able to dig into your isolated storage location

of your computer and (after a bit of hunting) discover the MyData.txt file (see Figure 20-24). If you were to open this file in notepad.exe, you would of course see the two lines of textual data.

Of course, you can layer into an IsolatedStorageFileStream object a Stream-derived type. For example, if you would rather write out data in a binary format, simply make use of the BinaryWriter rather than StreamWriter.

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

705

Figure 20-24. Our text file placed in isolated storage

Reading Data from Storage

Reading data from a user’s store is also very simple. Consider the following new method (which I am assuming you will also call from Main() after the call to the WriteTextToIsoStorage() method) that will read the data within the MyData.txt file and display it to the console window:

private static void ReadTextFromIsoStorage()

{

using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly())

{

using (IsolatedStorageFileStream isStream

=new IsolatedStorageFileStream("MyData.txt", FileMode.Open, FileAccess.Read, store))

{

// Layer into StreamReader.

using (StreamReader sr = new StreamReader(isStream))

{

string allTheData = sr.ReadToEnd(); Console.WriteLine(allTheData);

}

}

}

}

Deleting User Data from Storage

The IsolatedStorageFile type supplies two mechanisms for deleting user stores. The instance-level Remove() deletes the store that calls it. The static method IsolatedStorageFile.Remove() method takes the IsolatedStorageScope.User value and deletes all stores for the user running the code. For example, the following code deletes the active store and destroys all contents:

706 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

// Remove data for current store for the current user.

IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly();

store.Remove();

while the following code destroys all stores for the current user (this is the same behavior as seen when supplying the /remove flag to storeadm.exe):

// Remove ALL stores for current user.

IsolatedStorageFile.Remove(IsolatedStorageScope.User);

Creating a Custom Directory Structure

The current code examples did not create a unique hierarchy to contain the various data files. Rather, the MyData.txt file was placed directly in the root of the store (in many cases, this is exactly what you required). If you wish to create unique subdirectories, you do so using the instance-level CreateDirectory() method. Be aware that there is no object representation of a subdirectory within isolated storage. Instead, you pass in a string that represents the directories to be created. Once you do, CreateDirectory() returns an IsolatedStorageFile type that represents the access to the most nested directory. Consider the following code:

private static void CreateStorageDirectories()

{

// Forward slashes and backwards slashes are acceptable. using (IsolatedStorageFile store =

IsolatedStorageFile.GetUserStoreForAssembly())

{

store.CreateDirectory(@"MyDir\XmlData");

store.CreateDirectory("MyDir\\BinaryData");

store.CreateDirectory("MyDir/TextData");

}

}

If you call this method from within Main(), you will now be able to find the directory structure shown in Figure 20-25 under your isolated storage area.

Figure 20-25. Establishing a directory structure for a given store

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

707

Source Code The SimpleIsoStorage project is included under the Chapter 20 subdirectory.

Isolated Storage in Action: ClickOnce Deployment

At this point, isolated storage might seem like little more than a unique approach to persisting application data on a per-user level (which is very useful in its own right). However, recall that one of the problems this API solves is how to allow applications that do not run under the umbrella of Full Trust security to persist data in a safe manner.

To close this chapter, assume you have a Windows Forms application (named FileOrIsoStorageWinApp) that defines a Form containing two buttons. Chapter 27 examines the details of the Windows Forms API; however, if you are following along, handle the Click event for each Button type.

The first button will attempt to save data to the local hard drive using standard file IO techniques (be sure to import the System.IO and System.IO.IsolatedStorage namespaces in your code file):

private void btnFileIO_Click(object sender, EventArgs e)

{

using (StreamWriter sw = new StreamWriter(@"C:\MyData.txt"))

{

sw.WriteLine("This is my data."); sw.WriteLine("Cool, huh?");

}

}

The second button will write the same data to a file in isolated storage. The implementation of this Click event handler is identical to the WriteTextToIsoStorage() method you created in the previous project; however, here it is again for your convenience:

private void btnIsoStorage_Click(object sender, EventArgs e)

{

//Open up isolated storage based on identity of

//user + assembly evidence.

using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly())

{

// Now create an IsolatedStorageFileStream type. using (IsolatedStorageFileStream isStream

=new IsolatedStorageFileStream("MyData.txt", FileMode.OpenOrCreate, store))

{

//Layer this stream into a StreamWriter

//and write out some text.

using (StreamWriter sw = new StreamWriter(isStream))

{

sw.WriteLine("This is my data."); sw.WriteLine("Cool, huh?");

}

}

}

}

708 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

The IsolatedStorageFilePermission Attribute

Now, before we test our application, add the following using directive in the C# file defining your initial Form-derived type:

using System.Security.Permissions;

This namespace defines a number of security-centric attributes that can be applied to your application to inform the security subsystem which security settings a given assembly requires to operate correctly (among other details). Here, we wish to inform the CLR that our application requires, at minimum, assembly-level store isolation permissions. This can be achieved by adding the following assembly-level attribute to the code file of your Form-derived type:

[assembly: IsolatedStorageFilePermission(SecurityAction.RequestMinimum, UsageAllowed = IsolatedStorageContainment.AssemblyIsolationByUser)]

Now, when you compile and run this application directly within Visual Studio 2008 (via Ctrl+F5), you will see that clicking either button results in the creation of a new file with blobs of textual data. This is because the application has loaded from My_Computer_Zone, which as you recall grants Full Trust privileges to the assembly.

Constraining the Security Zone

Let’s deploy this application in such a way as to load it into Internet_Zone using a more restrictive permission set. To do so, we will deploy our application using ClickOnce deployment. As you might know, ClickOnce is a way to deploy an executable application to an end user’s machine via a remote web server. The remote application is hosted within an IIS virtual directory, which can be downloaded and installed to a local machine simply by using a web browser to point to the URL.

Note Full coverage of ClickOnce is beyond the scope of this chapter. If you have never deployed an application in this manner, simply follow the instructions I provide next (and consult the .NET Framework 3.5 SDK documentation for further details if you so choose).

To begin, open your project’s Properties page by double-clicking the Properties icon of Solution Explorer. Once you have done so, click the Security tab. By default, ClickOnce applications are deployed with Full Trust, and therefore they have all the security privileges as a local application installed using traditional means.

Here, we want to build a deployment script that will force our program to run under the Internet zone, which as you recall does not allow access to the hard drive using standard file IO operations. To do so, click the Enable Click Once Security Settings check box, select the This is a partial trust application radio button, and select Internet from the zone drop-down list box. Last but not least, click the Calculate Permissions button at the bottom of the Security configuration page. This will calculate the final permission set required by your application (see Figure 20-26).

CHAPTER 20 FILE I/O AND ISOLATED STORAGE

709

Figure 20-26. Constraining our application’s security zone

Publishing the Application to a Web Server

Now, click the Publish tab of the Properties editor. Click the Publish Wizard button on the lower part of this page, and on the first page of this wizard, enter the following URL to specify a new IIS virtual directory on your local machine to host this application:

http://localhost/MyIsolatedStorageApp/

The remaining options of the tool may be left at their defaults. Once you click the Finish button, your web browser will load the autogenerated publish.htm file. This is what end users will see when navigating to the remote web server that contains the application they wish to download and install locally (and yes, this web page can be customized to your heart’s content; you’ll find this file and related content under your project’s bin\Debug folder). Click the Install button and run the setup.exe application (and accept each security prompt). After a moment or two, the application will install to an area of the user’s machine named the ClickOnce cache and the program should launch.

Viewing the Results

Given that we have configured this application to run under restricted security, if you click the button that attempts to save data using the System.IO types, you will find the security exception shown in Figure 20-27.

710 CHAPTER 20 FILE I/O AND ISOLATED STORAGE

Figure 20-27. Security breach! Can’t access local file system when running in the Internet zone.

However, if you click the button that saves data to isolated storage, the application runs as expected. That wraps up our look at the isolated storage API and our introductory look at the Code Access Security model. While there is much more that could be said about CAS, as you have seen, using the types of System.IO.IsolatedStorage is very simple, as they are really just an extension of the file IO primitives.

Source Code The FileOrIsoStorageWinApp project is included under the Chapter 20 subdirectory.

Summary

This chapter began by examining the use of the Directory(Info) and File(Info) types. As you learned, these classes allow you to manipulate a physical file or directory on your hard drive. Next, you examined a number of types derived from the abstract Stream class, specifically FileStream. Given that Stream-derived types operate on a raw stream of bytes, the System.IO namespace provides numerous reader/writer types (StreamWriter, StringWriter, BinaryWriter, etc.) that simplify the process. Along the way, you also checked out the functionality provided by DriveType, and you learned how to monitor files using the FileSystemWatcher type and how to interact with streams in an asynchronous manner.

The second part of this chapter introduced you to the topic of isolated storage. As explained, this API allows a program to read and write data in a safe sandbox, even if the application has been loaded in a constrained security environment. While the programming model is very straightforward (if you have a grasp of basic file IO), the surrounding topics add some level of complexity. Given this, you also were given a whirlwind tour of Code Access Security.

Here, you learned that assemblies present evidence to the CLR at the time they are loaded into an application domain. At this point, they are assigned a code group that has a default set of permissions. The interesting aspect of CAS as it relates to file IO is that if an application is not granted Full Trust, use of the traditional IO operations result in a security exception. However, using isolated storage, your programs can persist data on a per-user level in a safe manner.

C H A P T E R 2 1

Introducing Object Serialization

In Chapter 20, you learned about the functionality provided by the System.IO namespace and the role of isolated storage (using the types of System.IO.IsolatedStorage). As shown, these namespaces provide numerous readers and writers that can be used to persist data to a given location (in a given format). This chapter examines the related topic of object serialization. Using object serialization, you are able to persist and retrieve the state of an object to (or from) any System.IO.Stream- derived type (including the IsolatedStorageFileStream type).

The ability to serialize types is critical when attempting to copy an object to a remote machine via various remoting technologies such as the .NET remoting layer, XML web services, and Windows Communication Foundation. Understand, however, that serialization is quite useful in its own right and will likely play a role in many of your .NET applications (distributed or not). Over the course of this chapter, you will be exposed to numerous aspects of the .NET serialization scheme, including a set of attributes (and interfaces) that allow you to customize the process.

Understanding Object Serialization

The term serialization describes the process of persisting (and possibly transferring) the state of an object into a stream (file stream, memory stream, etc.). The persisted data sequence contains all necessary information needed to reconstruct (or deserialize) the state of the object for use later. Using this technology, it is trivial to save vast amounts of data (in various formats) with minimal fuss and bother. In fact, in many cases, saving application data using serialization services results in less code than making use of the readers/writers found within the System.IO namespace.

For example, assume you have created a GUI-based desktop application and wish to provide a way for end users to save their preferences (window color, font size, etc.). To do so, you might define a class named UserPrefs that encapsulates 20 or so pieces of field data. Now, if you were to make use of a System.IO.BinaryWriter type, you would need to manually save each field of the UserPrefs object. Likewise, when you wished to load the data from a file back into memory, you would need to make use of a System.IO.BinaryReader and (once again) manually read in each value to reconfigure a new UserPrefs object.

While this is certainly doable, you would save yourself a good amount of time simply by marking the UserPrefs class with the [Serializable] attribute:

[Serializable]

public class UserPrefs

{

// Various points of data...

}

711