Introduction to the Infragistics Compression Library

[Infragistics] Mihail Mateev / Thursday, May 6, 2010

This article describes how to use the Infragistics Compression Library from NetAdvantage v10.1 Silverlight Line of Business controls

Compression Library supports creating, opening and extracting a zip file in your Silverlight application.

It is used for upload and download files to the server using different technologies: WCF, WCF Ria Services, Web service (.asmx), HTML Form for File Uploading with ASP.Net etc.
In this article I will use a Silverlight-enabled WCF service and demonstrate most used features of the Infragistics Silverlight Compression Library.

In order to successfully compile and run the application you will need to install trial version of NetAdvantage for Silverlight LoB 10.1
Probably most used scenario with Compression library is file transfer application between the client and the server (download, upload), where is important to
transfer compressed files to decrease the traffic.
Demo application demonstrates how to compress and decompress files, using password and different compression level.

There are several steps to create a demo upload/download application :

  1. Download the trial version of NetAdvantage for Silverlight LoB 10.1
  2. Create a Silverlight application, hosted in an ASP.Net application
  3. Add a Silverlight-enabled WCF service
  4. Implement Saving/Reading in the WCF service
  5. Implement client Silverlight application - add reference to Compression library, implement upload and download files, compress and decompress using password and different compression level.
  6. Implement Isolated Storage management - to be possible to use this sample with both Silverlight 3 and Silverlight 4 saving the temporary files is done using an Isolated Storage. (Silverlight 4 supports saving directly into the client file system.

Requirements:

  1. Visual Studio 2010 or Visual Studio 2008 (sample application is created with Visual Studio 2010)
  2. Silverlight 3(for Visual Studio 2008 you need Silverlight Tools for Visual Studio 2008)  or Silverlight 4 (Silverlight Tools RC2 for Visual Studio 2010)

Create a Silverlight Application:

Create a Silverlight application, hosted in an ASP.Net application.

Add a reference to Infragisrics.Silverlight.Compression.v10.1 library

Add a Silverlight-enabled WCF Service 

To be possible to upload larger files than KB via Silverlight enabled WCF service you need to change the Web.Config:

Default auto generated Web.config .

<?xml version="1.0"?> 
<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>

<system.serviceModel>
    <behaviors>
        <serviceBehaviors>
            <behavior name="">
                <serviceMetadata httpGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <bindings>
        <customBinding>
            <binding name="CompressionLibraryApp.Web.UploadService.customBinding0">
                <binaryMessageEncoding />
                <httpTransport />
            </binding
        </customBinding>
    </bindings>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="truemultipleSiteBindingsEnabled="true" />
    <services>
        <service name="CompressionLibraryApp.Web.UploadService">
            <endpoint address="" binding="customBinding" bindingConfiguration="CompressionLibraryApp.Web.UploadService.customBinding0"
                contract="CompressionLibraryApp.Web.UploadService" />
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        </service>
    </services>
</system.serviceModel>
</configuration>

 Change the code inside <binding>...</binding> section in the original Web.config file
with this one:

 <binding name="CompressionLibraryApp.Web.UploadService.customBinding0">
  <binaryMessageEncoding>
    <readerQuotas  maxArrayLength="2147483647"/>
  </binaryMessageEncoding>
  <httpTransport maxReceivedMessageSize="2147483647" />
</binding>

 Add in <appSetting>  </appSetting>  section in your Web.config file new key DataDirectory.

It will be used to compose the full path to Data folder in the ASP.Net application.

 <appSettings>
  <add key="DataDirectory" value="Data"/>
</appSettings>

Implement methods for download and save files in the UploadService

 In the UploadService class add three methods:

  • For download:

/// <summary>
/// Downloads the file with specified file name.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <returns> Returns the file like a byte array </returns>
[OperationContract]
public byte[] Download(string fileName)
{
        try
        {
            string imagePath = HttpContext.Current.Server.MapPath(".") + Path.DirectorySeparatorChar +
                ConfigurationManager.AppSettings["DataDirectory"] + Path.DirectorySeparatorChar +
                                fileName;
            if (File.Exists(imagePath))
            {
                FileStream fileStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
                BinaryReader reader = new BinaryReader(fileStream);
                byte[] imageBytes = reader.ReadBytes((int)fileStream.Length);
                return imageBytes;
            }
            return null;
        }
        catch (Exception)
        {
            return null;
        }
}

  •  Get an array of file names on the server in the specified folder
/// <summary>
/// Gets the entries in the Data folder.
/// </summary>
/// <returns></returns>
[OperationContract]
public string[] GetEntries()
{
        string foldername = HttpContext.Current.Server.MapPath(".") + Path.DirectorySeparatorChar + ConfigurationManager.AppSettings["DataDirectory"];
        string[] files = Directory.GetFiles(foldername, "*.*");
        //foreach(string entry in files)
        for (int i = 0; i < files.Count(); i++)
        {
            string entry = files[i];
            int index = entry.LastIndexOf("\\") + 1;
            files[i] = entry.Substring(index, entry.Length - index);
        }
        return files;
}
  •  Save a file , sent like a byte array on the server.
/// <summary>
/// Uploads a file with specified file name
/// and content like a byte array.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <param name="file">The file.</param>
[OperationContract]
public void UploadFile(string fileName, byte[] file)
{
        string foldername = HttpContext.Current.Server.MapPath(".") + Path.DirectorySeparatorChar +
            ConfigurationManager.AppSettings["DataDirectory"] + Path.DirectorySeparatorChar;
        string strFilePath = foldername + fileName;
        //create a filestream
        FileStream fs = new FileStream(strFilePath, FileMode.Create, FileAccess.Write);
        //write the file at the specified location
        fs.Write(file, 0, file.Length);
        fs.Close();
        fs.Dispose();
}

 Add a service reference in the Silverlight client application.

 

Create a user interface of the application:

Download: Silverlight application will show a list of files in the "Data" folder on the server. Client can download selected file from the list. If file is a zip file, a  ZipFile instance will be created via Compression library and file names from archive will be placed in a second list box.  If selected file is an image - it will be saved into Isolated storage and displayed in a preview image control.

 

 Add a ListBox that will show a list with file names in the "Data" folder on the server.

When list box is loaded is called method GetEntresAsync() that returns an array of file names from the server:

private void FilesListBox_Loaded(object sender, RoutedEventArgs e)
{
    this.RefreshFilesList();
}

//Updates a list with file names from the server
private void RefreshFilesList()
{
    UploadServiceClient client = new UploadServiceClient();
    client.GetEntriesAsync();
    client.GetEntriesCompleted += Client_GetEntriesCompleted;
    client.CloseAsync();
}
 
void Client_GetEntriesCompleted(object sender, GetEntriesCompletedEventArgs e)
{
    ObservableCollection<string> items = e.Result;
    this.FilesListBox.ItemsSource = items;
}

Selected file will be downloaded via WCF service in the Silverlight application like a byte array:

private void BtnDownload_Click(object sender, RoutedEventArgs e)
{
    this.ZipEntriesTextBox.Text = "";
    UploadServiceClient client = new UploadServiceClient();
    if (this.FilesListBox.SelectedItem == null)
    {
        return;
    }
    _fileName = this.FilesListBox.SelectedItem.ToString();
    client.DownloadAsync(_fileName);
    if (_fileName.EndsWith("zip"))
    {
        client.DownloadCompleted += Client_DownloadCompleted;
    }
    else
    {
        client.DownloadCompleted += Client_DownloadSimpleCompleted;
    }
     client.CloseAsync();
}

Client_DownloadSimpleCompleted is used when download file that is not zip archive.

void Client_DownloadCompleted(object sender, DownloadCompletedEventArgs e)
{
    _zipArray = e.Result;
    //Check if password is set in the PassZipPassword TextBox
    string password = this.PassZipPassword.Password.Trim();
    // Create a ZipFile instance
    if (!password.Equals(string.Empty))
    {
        _zip = new ZipFile(_zipArray, password);
    }
    else
    {
        _zip = new ZipFile(_zipArray);
    }
    //ZipFile.Entries returns an ObservableCollection<ZipEntry> instance
    this.ZipEntriesListBox.ItemsSource = this._zip.Entries;
    if (this.ZipEntriesListBox.Items.Count > 0)
    {
        this.ZipEntriesListBox.SelectedIndex = 0;
    }
    this.ZipEntriesTextBox.Text = "Zip Download Completed!";
}

If selected file is an image it will be in a preview image control.

private void ZipEntriesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    //Check if the selected item is a ZipEntry
    ZipEntry entry = this.ZipEntriesListBox.SelectedItem as ZipEntry;
    if (entry != null)
    {
        try
        {
            //Get the content of the ZipEntry as a CrcCalculatorStream
            CrcCalculatorStream crcStream = entry.OpenReader();
            byte[] buffer = new byte[crcStream.Length];
            int n = crcStream.Read(buffer, 0, buffer.Length);
            // CrcCalculatorStream can not be used directly like a BitmaImage source
            // We need first create a file structure (save file) and after that 
            WriteDataToStore(buffer, entry.FileName);
            //Read from the Isolated Storage
            byte[] result = ReadDataFromStore(entry.FileName);
            MemoryStream str = new MemoryStream(result);
            BitmapImage bitmap = new BitmapImage();
            bitmap.SetSource(str);
           
this.ImgPreview.Source = bitmap;
            str.Close();
            this.ZipEntriesTextBox.Text = entry.FileName;
        }
        catch (NullReferenceException)
        {
            MessageBox.Show("Possible incorrect password!");
            this.ImgPreview.Source = null;
        }
    }
    else
    {
        this.ImgPreview.Source = null;
    }
}

There are several helper methods to read and save data to the Isolated Storage.
In Silverlight 4 it is possible to write into some folders from the client file system.
Sample application uses an Isolated Storage to be possible to use the sample with both Silverlight 3 and Silverlight 4.

To create a file structure you need to write created ZipFile object to a file.
After saving file can be read and uploaded like a byte array.

 Writing a file to an Isolated Storage:

// Writes a file to an Isolated IStrorage, specified via its name
// and content like a byte array
private static void WriteDataToStore(byte[] data, string fileName)
{
    if (data == null || data.Length == 0)
        return;
    using (var store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        CleanStorage(store);
        IsolatedStorageFileStream fileStream = store.CreateFile(fileName);
        fileStream.Write(data, 0, data.Length);
        fileStream.Close();
    }
}

Writing a ZipFile instance to an Isolated Storage:
// Writes a zip file to an Isolated IStrorage, specified via its name
// and content like a byte array
private static void WriteZipToStore(ZipFile zipFile, string fileName)
{
    if (zipFile == null || fileName.Equals(String.Empty))
    {
        return;
    }
    using (var store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        // Cleans an Isolated Storage to ensure that we will have enough space there
        CleanStorage(store);
        IsolatedStorageFileStream fileStream = store.CreateFile(fileName);
        // Zip file must be saved via ZipFIle.Save(Stream)
        zipFile.Save(fileStream);
        fileStream.Close();
    }
}

Reading a file from an Isolated Storage:

// Reads a file from the Isolated Storage, specified by name
private static byte[] ReadDataFromStore(string fileName)
{
    using (var store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        IsolatedStorageFileStream fileStream;
        fileStream = store.OpenFile(fileName, FileMode.Open);
        byte[] b = new byte[fileStream.Length];
        fileStream.Read(b, 0, (int)fileStream.Length);
        fileStream.Close();
        return b;
    }
}

Selected file (selected item from the list with downloaded items) can be saved/extracted:

private void BtnExtractSelectedEntry_Click(object sender, RoutedEventArgs e)
{
    if (this._zip != null && this._fileName.EndsWith("zip"))
    {
        _saveDialog = new SaveFileDialog();
        ZipEntry entry = this.ZipEntriesListBox.SelectedItem as ZipEntry;
        if (entry == null)
        {
            return;
        }
        int index = entry.FileName.LastIndexOf(".");
        string ext = entry.FileName.Substring(index + 1);
        string filter = "Image files | *." + ext;
        _saveDialog.Filter = filter;
        bool? dialogResult = _saveDialog.ShowDialog();
        if (dialogResult == true && this.ZipEntriesListBox.SelectedItems.Count > 0)
        {
            CrcCalculatorStream crcStream = entry.OpenReader();
            byte[] buffer = new byte[crcStream.Length];
            int n, totalBytesRead = 0;
            do
            {
                n = crcStream.Read(buffer, 0, buffer.Length);
                totalBytesRead += n;
            } while (n > 0);
            using (Stream fs = (Stream)_saveDialog.OpenFile())
            {
                fs.Write(buffer, 0, totalBytesRead);
                fs.Close();
                this.ZipEntriesTextBox.Text = "File Saved!";
            }
        }
    }
    else if (this._zipArray != null && this._fileName != null && !this._fileName.Equals(string.Empty))
    {
        SaveFileDialog dialog = new SaveFileDialog();
        string filter = "All files | *.*";
        dialog.Filter = filter;
        bool? dialogResult = dialog.ShowDialog();
        if (dialogResult == true && this.ZipEntriesListBox.SelectedItems.Count > 0)
        {
            using (Stream fs = (Stream)dialog.OpenFile())
            {
                fs.Write(this._zipArray, 0, this._zipArray.Length);
                fs.Close();
                this.ZipEntriesTextBox.Text = "File Saved!";
            }
        }
    }
}

 

Upload: Silverlight application can upload any file to the server (simple file upload). It is possible also to select multiple files, zip it (it is possible to set the password and compression level), and upload the archive to the server.

Simple file upload uploads a standalone file to the server without zip it. Simple upload is implemented in the sample demo application.

Zip and Upload Files - select multiple files, zip it and upload it.

private void BtnZipAndUploadFiles_Click(object sender, RoutedEventArgs e)
{
    _openDialog = new OpenFileDialog { Multiselect = true, Filter = "All Files|*.*" };
    bool? dialogResult = _openDialog.ShowDialog();
    int k = 0;
    if (dialogResult == true)
    {
        if (this.TxtZipFileName.Text.Equals(string.Empty))
        {
            MessageBox.Show("Empty zip file name!");
            return;
        }
        ZipFile zipFile = new ZipFile();

        //Check for selected compression level
        zipFile.CompressionLevel = (CompressionLevel)(((KeyValuePair<Enum, string>)this.ComboCompLevel.SelectedItem).Key);
        //Set the password
        string password = this.PassZipPassword.Password.Trim();
        if (!password.Equals(string.Empty))
        {
            zipFile.Password = password;
        }
        foreach (FileInfo file in _openDialog.Files)
        {
            // Add the file to the ZipFile object
            Stream stream = file.OpenRead();
            _arrays = new byte[_openDialog.Files.Count()][];
            _arrays[k] = new byte[stream.Length];
            int numBytes = stream.Read(_arrays[k], 0, (int)stream.Length);
            ZipEntry entry = ZipEntry.CreateFile(file.Name, System.IO.Path.GetDirectoryName(file.Name), _arrays[k]);
            zipFile.Entries.Add(entry);
            k++;
        }
        string fileName = this.TxtZipFileName.Text;
        MemoryStream strm = new MemoryStream();
        zipFile.Save(strm);
        //Ensure that into Isolated Storage has enough space
        long requestedQuotaSize = strm.Length + 524288;
        using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if(isf.Quota < requestedQuotaSize)
            {
                MessageBox.Show("Storage is not enough! Increase it to " + requestedQuotaSize.ToString());
                TxtQuota.Text = requestedQuotaSize.ToString();
                BtnIncreaseQuota.Focus();
                return;
            }
        }
                
        //Write ZipFile to the Isolated storage
        //We need to save file first before upload it
        WriteZipToStore(zipFile, fileName);
        //Read saved 
        byte[] buffer = ReadDataFromStore(fileName);
        UploadFile(buffer, fileName);
    }
}

 

UploadFile method is a helper method, used to create a WCF service client and upload asynchronously a file, passed via parameter.

private void UploadFile(Stream strm, string fileName)
{
    byte[] buffer = new byte[strm.Length];
    strm.Read(buffer, 0, (int)strm.Length);
    strm.Close();
    UploadFile(buffer, fileName);
}
private void UploadFile(byte[] buffer, string fileName)
{
    UploadServiceClient client = new UploadServiceClient();
    client.UploadFileCompleted += ClientUploadFileCompleted;
    client.UploadFileAsync(fileName, buffer);
}
Event handler ClientUploadFileCompleted is used display the result from uploading.

  private void ClientUploadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
    if (e.Error == null)
    {
        this.ZipEntriesTextBox.Text = _openDialog.File.Name + "Successfully Saved at Server Location";
        this.RefreshFilesList();
    }
    else
    {
        this.ZipEntriesTextBox.Text = e.Error.Message;
    }
}

 

Some specific details when upload multiple files:

Zip files must be saved firstly in a temporary folder to be created a zip file structure and after that read again and sent the temporary files. It is possible Isolated Storage quota to be not enough.  

In this demo application

It is possible an Isolated Storage quota to be not enough large.

In the BtnZipAndUploadFiles_Click method there is a space validation:

//Ensure that into Isolated Storage has enough space
long requestedQuotaSize = strm.Length + 524288;
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
    if(isf.Quota < requestedQuotaSize)
    {
        MessageBox.Show("Storage is not enough! Increase it to " + requestedQuotaSize.ToString());
        TxtQuota.Text = requestedQuotaSize.ToString();
        BtnIncreaseQuota.Focus();
        return;
    }
}

 

 //Increases an Isolated Storage quota
private void BtnIncreaseQuota_Click(object sender, RoutedEventArgs e)
{
    if(!this.TxtQuota.Text.Equals(String.Empty))
    {
        try
        {
            long quota = System.Convert.ToInt64(this.TxtQuota.Text);
            IncreaseStorage(quota);
        }
        catch (FormatException ex)
        {
            MessageBox.Show(ex.Message);
        }
        this.TxtQuota.Text = "";
    }
}
 
//Tries to increase an Isolated Storage quota
private void IncreaseStorage(long spaceRequest)
{
    using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
    {
        try
        {
            if (true == isf.IncreaseQuotaTo(spaceRequest))
            {
                Results.Text = "Isolated Storage Quota is " + isf.Quota.ToString();
            }
            else
            {
                Results.Text = "Insufficient Quota :" + isf.Quota.ToString();
            }
        }
        catch (Exception e)
        {
            Results.Text = "An error occurred: " + e.Message;
        }
    }
}

 

Updating the quota needs user interaction because of security restrictions:

 

 Uploaded zip file with multiple images - to upload a zip file you need to set the zip name , password(optional) and compression level (optional):

Infragistics Silverlight Compression Library you can download here: