Chapter 14. Reading and Writing Local Files

One of the (many) things HTML cannot do is read arbitrary files from the client’s machine or even create or delete such files. Rightfully so, you may say, since severe security precautions are involved. However, there are some scenarios where at least a limited read or write access is desirable. Imagine a Silverlight support application that allows users to upload error logs to the server that then processes this data. Or imagine a highly personalized, Silverlight-driven web portal that wants to save user settings on the client machine. In such cases, Silverlight provides two techniques that will help you implement a good solution for those scenarios. Just keep in mind that there are several security precautions in place. So as a user, don’t worry, but as a developer, be prepared to take a few extra things into consideration when implementing file access.

Accessing Local Files

For obvious security reasons, Silverlight does not allow direct access to files on the client’s machine. However, in combination with the OpenFileDialog class, you can get read access in the form of a FileStream object. Users do have to specifically grant access to files, and the only data the Silverlight application receives is the data from the files, not any meta information. In our sample application, we will allow users to select a video file from their hard drives and grant Silverlight access to it; the video is then shown as part of the application.

Let’s start with the (simple) XAML-based UI: a button for the open file dialog to open, and a <MediaElement> element that’s currently empty. Look at Example 14-1 for the result.

Example 14-1. Accessing local files, the XAML file (Page.xaml, project FileUpload)

<UserControl x:Class="FileUpload.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <StackPanel x:Name="LayoutRoot" Background="White">
        <Button Content="Upload!" Click="UploadFile" />
        <MediaElement x:Name="video" />
    </StackPanel>
</UserControl>

Caution

You can only launch the open file dialog upon a user interaction, such as a mouse click!

In the code-behind file, you first instantiate the OpenFileDialog class and set some properties, such as Filter (a list of file extensions and associated descriptions to be available in the dialog). A call to the ShowDialog() method then opens the dialog:

OpenFileDialog d = new OpenFileDialog();
d.Filter = "WMV video files|*.wmv|ASF video files|*.asf";
bool? selected = d.ShowDialog();

You can see the result of that code snippet in Figure 14-1. Notice that the file type list is populated with WMV and ASF files, as we implemented it.

Silverlight’s open file dialog

Figure 14-1. Silverlight’s open file dialog

When the user actually does choose a file, the return value of that call is true. The File property grants us access to specific information about the file, most importantly via the OpenRead() method, which returns a read-only FileStream object. This object can then be used as a source for the MediaElement control:

if (selected.Value)
{
    video.SetSource(d.File.OpenRead());
}

That’s it! The complete code is shown in Example 14-2, and the output is shown in Figure 14-2.

Example 14-2. Accessing local files, the C# file (Page.xaml.cs, project FileUpload)

using System.Windows;
using System.Windows.Controls;

namespace FileUpload
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
        }

        private void UploadFile(object sender, RoutedEventArgs e)
        {
            OpenFileDialog d = new OpenFileDialog();
            d.Filter = "WMV video files|*.wmv|ASF video files|*.asf";
            bool? selected = d.ShowDialog();

            if (selected.Value)
            {
                video.SetSource(d.File.OpenRead());
            }
        }
    }
}
The (local) video is playing

Figure 14-2. The (local) video is playing

Note

Since the application’s project is called FileUpload, a quick note on actual file uploads—once you have retrieved the content of the local file, you can also send it to the remote server, as discussed in Chapter 13.

Using Isolated Storage

A well-known and established technology already allows a web application to store data on a client machine: cookies. This hack for the HTTP protocol was originally conceived by Netscape (see http://cgi.netscape.com/newsref/std/cookie_spec.html for the original specification), and is somewhat limited: you can store only text information, up to 4,096 bytes. Other web technologies such as Adobe Flash have implemented their own solutions to this problem; they use their own mechanisms and let the plug-in store data in the user’s profile. The Silverlight team implemented a similar approach and called it isolated storage or application storage. If the user allows it, the Silverlight application may store data on the client’s machine, not in any arbitrary folder, but tucked away in a specific place in the user’s profile. In this section we will expand the previous example in this chapter and store the video the user selected in Silverlight’s isolated storage. You will learn all the important features and limitations of this approach along the way.

We start once again with the UI. In contrast to Example 14-1, the markup in Example 14-3 adds two more buttons: one to delete a file, and one to increase a quota. More on this shortly.

Example 14-3. Using isolated storage, the XAML file (Page.xaml, project IsolatedStorage)

<UserControl x:Class="IsolatedStorage.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <StackPanel x:Name="LayoutRoot" Background="White">
        <Button Content="Upload!" Click="UploadFile" x:Name="UploadButton" />
        <Button Content="Confirm quota increase!" Click="IncreaseQuota" 
                x:Name="QuotaButton" Visibility="Collapsed" />  
        <Button Content="Delete!" Click="DeleteFile" />
        <Canvas x:Name="VideoPlaceholder"></Canvas>
    </StackPanel>
</UserControl>

Let’s discuss the C# implementation in the code-behind file. We will start once again with the button to “upload” a file, that is, to load a local file courtesy of the OpenFileDialog class. The general approach is as follows: the user selects a file, the file is then saved into isolated storage, and finally the “uploaded” video is shown. If there is already a video in isolated storage, it is deleted. Here is a typical implementation:

OpenFileDialog d = new OpenFileDialog();
d.Filter = "WMV video files|*.wmv|ASF video files|*.asf";
bool? selected = d.ShowDialog();

if (selected.Value)
{
    DeleteFile();
    FileStream _uploadStream = d.File.OpenRead();
    SaveVideo();
    ShowVideo();
}

However, there is a catch. If you run this example, chances are that you will receive an error message or exception, as shown in Figure 14-3. The reason: Silverlight’s isolated storage has a quota system in place, i.e., only a certain amount of data may be saved. This amount is controlled by the Silverlight plug-in and is also available in the Silverlight configuration.

Too little space is available

Figure 14-3. Too little space is available

There is a programmatic approach to increasing this quota. First of all, you need to access the isolated storage store specifically for this application (Silverlight provides options to bind isolated storage to specific applications or to complete web sites/domains):

IsolatedStorageFile filestore = IsolatedStorageFile.GetUserStoreForApplication();

The property Quota contains the total space in the current isolated storage, whereas AvailableSpace returns the amount of free space. The method IncreaseQuotaTo() prompts the user to increase the isolated storage to a given amount. In our specific case, if might look as follows (but read on for a catch):

if (filestore.AvailableFreeSpace < _uploadStream.Length)
{
    filestore.IncreaseQuotaTo(
        filestore.Quota + _uploadStream.Length - filestore.AvailableFreeSpace);
}

The catch is that increasing the quota can only be triggered by user interaction. Selecting a file in an open file dialog is unfortunately not an eligible user interaction. So you have to prompt the users to do a mouse click. That’s what the “Increase quota” button is good for. If the available space in isolated storage does not suffice for the current file, the button is displayed (see Figure 14-4):

UploadButton.Visibility = Visibility.Collapsed;
QuotaButton.Visibility = Visibility.Visible;
The extra button appears

Figure 14-4. The extra button appears

Once the user actually clicks on the button, the quota is increased. To be more exact, the user is asked whether the quota may be increased. In some cases, increasing the quota to exactly the amount of space required can lead to an “insufficient space” exception being thrown by Silverlight, so just to be safe, we increase the quota by twice the required amount. The return value of the IncreaseQuotaTo() is a boolean value stating whether the user agreed or disagreed with the quota prompt (which also can be seen in Figure 14-5):

if (!filestore.IncreaseQuotaTo(filestore.Quota + 
    2 * (_uploadStream.Length - filestore.AvailableFreeSpace)))
{
    return;  // or throw an exception
}
else
{
    UploadButton.Visibility = Visibility.Visible;
    QuotaButton.Visibility = Visibility.Collapsed;
    SaveVideo();
    ShowVideo();
}
Silverlight’s isolated storage wants more available space

Figure 14-5. Silverlight’s isolated storage wants more available space

Saving the video to isolated storage is implemented by the CreateFile() method. It returns a special kind of stream, IsolatedStorageFileStream, where you can then write the data. Instead of creating a file, you can also create a directory (using the CreateDirectory() method) and therefore create your own local file organization:

using (IsolatedStorageFileStream iso = filestore.CreateFile("video"))
{
    byte[] b = new byte[_uploadStream.Length];
    _uploadStream.Read(b, 0, b.Length);
    iso.Write(b, 0, b.Length);
}

Note

We are using a using statement to make sure that the IsolatedStorageFileStream is closed after writing. Later in the flow of the application we want to read from the stream, and Silverlight does not allow concurrent access to the isolated store.

After the video has been saved in local storage, Silverlight’s configuration (available via the context menu of any Silverlight application) will show the used and available amounts in the Application Storage tab (see Figure 14-6). Technically, the data is stored within the C:Documents and SettingsusernameAppDataLocalLowMicrosoftSilverlightis folder, as Figure 14-7 shows.

Configuring isolated storage in Silverlight’s configuration

Figure 14-6. Configuring isolated storage in Silverlight’s configuration

The data is actually stored in the file system

Figure 14-7. The data is actually stored in the file system

The ShowVideo() method will then try to load a video from isolated storage and display it dynamically in the application. Two methods of the IsolatedStorageFile class come in handy here: FileExists() determines whether a file actually exists in local storage, and OpenFile() opens the file and returns an IsolatedStorageFileStream instance. This may then be used as a video source:

private void ShowVideo()
{
    IsolatedStorageFile filestore = IsolatedStorageFile.GetUserStoreForApplication();
    if (filestore.FileExists("video"))
    {
        MediaElement m = new MediaElement();
        _videoStream = filestore.OpenFile("video", FileMode.Open, FileAccess.Read);
        m.SetSource(_videoStream);
        VideoPlaceholder.Children.Clear();
        VideoPlaceholder.Children.Add(m);
    }
}

The one functionality missing is deleting the video from local storage. The IsolatedStorageFile class’s DeleteFile() method takes care of that. However, deletion will fail if the file is still playing or if the IsolatedStorageFileStream is still open. Therefore, you need to get rid of both the stream and the MediaElement first:

private void DeleteFile(object sender, RoutedEventArgs e)
{
    VideoPlaceholder.Children.Clear();
    if (_videoStream != null)
    {
        _videoStream.Close();
        _videoStream = null;
    }
    IsolatedStorageFile filestore = 
        IsolatedStorageFile.GetUserStoreForApplication();
    if (filestore.FileExists("video"))
    {
        filestore.DeleteFile("video");
    }
}

Example 14-4 contains the complete C# code. As you have learned in this section, there are quite a few things to take into consideration when working with isolated storage, but on the other hand, it gives you a good means to save data locally—if users let you.

Example 14-4. Using isolated storage, the C# file (Page.xaml, project IsolatedStorage)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;
using System.IO;

namespace IsolatedStorage
{
    public partial class Page : UserControl
    {
        private FileStream _uploadStream;
        private IsolatedStorageFileStream _videoStream = null;

        public Page()
        {
            InitializeComponent();
            ShowVideo();
        }

        private void UploadFile(object sender, RoutedEventArgs e)
        {
            IsolatedStorageFile filestore = 
                IsolatedStorageFile.GetUserStoreForApplication();
            OpenFileDialog d = new OpenFileDialog();
            d.Filter = "WMV video files|*.wmv|ASF video files|*.asf";
            bool? selected = d.ShowDialog();

            if (selected.Value)
            {
                DeleteFile(sender, e);
                _uploadStream = d.File.OpenRead();
                if (filestore.AvailableFreeSpace < _uploadStream.Length)
                {
                    PromptForQuota();
                }
                else
                {
                    SaveVideo();
                    ShowVideo();
                }
            }
        }

        private void DeleteFile(object sender, RoutedEventArgs e)
        {
            VideoPlaceholder.Children.Clear();
            if (_videoStream != null)
            {
                _videoStream.Close();
                _videoStream = null;
            }
            IsolatedStorageFile filestore = 
                IsolatedStorageFile.GetUserStoreForApplication();
            if (filestore.FileExists("video"))
            {
                filestore.DeleteFile("video");
            }
        }

        private void ShowVideo()
        {
            IsolatedStorageFile filestore = 
                IsolatedStorageFile.GetUserStoreForApplication();
            if (filestore.FileExists("video"))
            {
                MediaElement m = new MediaElement();
                _videoStream = filestore.OpenFile(
                    "video", FileMode.Open, FileAccess.Read);
                m.SetSource(_videoStream);
                VideoPlaceholder.Children.Clear();
                VideoPlaceholder.Children.Add(m);
            }
        }

        private void SaveVideo()
        {
            IsolatedStorageFile filestore = 
                IsolatedStorageFile.GetUserStoreForApplication();
            using (IsolatedStorageFileStream iso = 
                   filestore.CreateFile("video"))
            {
                byte[] b = new byte[_uploadStream.Length];
                _uploadStream.Read(b, 0, b.Length);
                iso.Write(b, 0, b.Length);
            }
        }

        private void PromptForQuota()
        {
            UploadButton.Visibility = Visibility.Collapsed;
            QuotaButton.Visibility = Visibility.Visible;
        }

        private void IncreaseQuota(object sender, RoutedEventArgs e)
        {
            IsolatedStorageFile filestore = 
                IsolatedStorageFile.GetUserStoreForApplication();
            if (!filestore.IncreaseQuotaTo(filestore.Quota + 
                2 * (_uploadStream.Length - filestore.AvailableFreeSpace)))
            {
                return;  // or throw an exception
            }
            else
            {
                UploadButton.Visibility = Visibility.Visible;
                QuotaButton.Visibility = Visibility.Collapsed;
                SaveVideo();
                ShowVideo();
            }
        }
    }
}

Further Reading

http://silverlightuk.blogspot.com/2008/04/isolated-storage-quotadat.html

Information on the files Silverlight uses to managed isolated storage

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset