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.
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>
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.
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()); } } } }
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.
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.
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;
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(); }
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); }
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
Settingsusername
AppDataLocalLowMicrosoftSilverlightis
folder, as Figure 14-7 shows.
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(); } } } }
Information on the files Silverlight uses to managed isolated storage