Implementing simple file upload using Silverlight 4 Drag & Drop feature and Infragistics Compression library

In this article we will learn how to use Infragistics Compression library for Silverlight by implementing a simple multi file upload application. We will use the Compression library to compress files before uploading, thus minimizing network bandwidth usage.

For those of you who don’t want to read the full story, you can download source code for the sample from here. In order to successfully compile and run the application you will need to install trial version of NetAdvantage for Silverlight LoB 10.1

 

In order to keep things simple with as less code as possible I will use the WCF RIA Services. It will automatically handle all asynchronous service calls, error handling, and almost zero code for implementing the Domain Service. Yes, sure we can use RIA Services to transfer files. And RIA Services does use msbin1 encoding for messages. Which means that the service messages are binary encoded, and no bloated XML tags are transferred over the wire. And we get all this for free with RIA Services.

So, where to begin from?

First we will need the Silverlight 4 Developer Runtime, Silverlight 4 Tools for Visual Studio 2010, Silverlight 4 SDK, and WCF RIA Services. You can download Silverlight 4 Tools & SDK from http://go.microsoft.com/fwlink/?LinkID=177428, while the WCF RIA Services & WCF RIA Services Toolkit from:  http://www.silverlight.net/getstarted/riaservices/  

Next we will need the Infragistics Compression library, part of NetAdvantage for Silverlight LoB 10.1 (the version for Silverlight 4 is part of the Service Release).

Let’s begin by just create a “Silverlight Application”. The only option that we shall not forget to check is “Enable WCF RIA Services”:

 

As I mentioned I will stick to the most simple example code possible, writing as less code as possible. I will take the approach using WCF RIA Services with POCO (Plain Old CLR Objects). So I need to create a class that will represent the uploaded files, and this will be my POCO object that will be handled by RIA Services. My class is called WebFile:

    public class WebFile

    {

        [Key]

        public string FileName { get; set; }

        public byte[] FileContent { get; set; }

        public bool IsCompressed { get; set; }

    }

 

In order to work with WCF RIA Service, a POCO object needs to have a Key defined. KeyAttribute is defined in System.ComponentModel.DataAnnotations namespace, part of System.ComponentModel.DataAnnotations assembly. For the sake of simplicity, our Key is the name of uploaded file. “What if I want to upload two files that have same name, but live in different folders?” you might ask. Well, although we get a FileInfo object from the DragEventArgs’s Data property, it yet very limited (due to security considerations), so we can’t get the FullName, nor we can get the DirectoryName. At the end we are working with only Name property of FileInfo object, and we can’t handle two files with different names, even if they have different Length. Well, we can, by adding an extra logic for renaming, but I give this for homework J

One more thing I would like to do on the server part, before implementing the actual Domain Service, is to create a simple FileHandler class, which will properly write the content of the file to a local folder:

    public static class FileHandler

    {

        public static void HandleFile(WebFile file)

        {

            string uploadDir = WebConfigurationManager.AppSettings["UploadDir"];

            if (!string.IsNullOrEmpty(uploadDir))

            {

                if (uploadDir.IndexOf("~/") == 0)

                    uploadDir = HttpContext.Current.Server.MapPath(uploadDir);

                if (uploadDir.LastIndexOf("/") == uploadDir.Length - 1)

                    uploadDir = uploadDir.Substring(0, uploadDir.Length - 1);

                string fullFileName = string.Format("{0}/{1}", uploadDir, file.FileName);

                if (file.IsCompressed)

                    fullFileName = fullFileName + ".zip";

                if (File.Exists(fullFileName))

                {

                    string ext = fullFileName.Substring(fullFileName.LastIndexOf("."));

                    string fName = fullFileName.Substring(0, fullFileName.LastIndexOf("."));

                    fullFileName = string.Format("{0}_1{1}", fName, ext);

                }

                File.WriteAllBytes(fullFileName, file.FileContent);

            }

        }

    }

 

Nothing scary – just take the location of server path where the file shall be saved, than making additional checks whether it is absolute or relative path, trailing slashes etc. Then, if the WebFile object is marked as compressed, we append “.zip” to the file name to be saved on disk. Than a very basic duplicate file name check is made, and simple automatic renaming is performed. And finally, the file content is written to the specified server location.

 

O.K. Let’s create the Domain Service now. Being on the web project, I will create a new folder “Services” and I will “add new item” in that folder. The item I want to use is “Domain Service Class”, found under “Web” category:

 

Since we do not have any Entity Models in our project, we have the only option to create an Empty Domain Service Class and Enable Client access to it:

 

For Domain Service to work, we need to implement at least one method:

        public IQueryable<WebFile> GetFiles()

        {

            string path = WebConfigurationManager.AppSettings["UploadDir"];

            List<WebFile> webFiles = new List<WebFile>();

            if (string.IsNullOrEmpty(path)) return webFiles.AsQueryable();

            DirectoryInfo di = new DirectoryInfo(HttpContext.Current.Server.MapPath(path));

            foreach (FileInfo file in di.GetFiles())

            {

                webFiles.Add(new WebFile { FileName = file.Name });

            }

            return webFiles.AsQueryable();

        }

This method will send a list of server files, which reside in designated upload folder, back to the client.

And the method that will handle the file upload will be:

        public void InsertFile(WebFile file)

        {

            FileHandler.HandleFile(file);

        }

 

Isn’t it simple, ah?

Defining these methods has standard annotation for implementing WCF RIA Service operations. By defining a method which has return type of IQueryable<WebFile> we basically say to the RIA that we will have WebFile objects that will be going between server and client.

 

Next step is to build the Web Application project and make sure everything compiles normally. The other reason to build is that the WCF RIA Services is generating code on the Silverlight application, under Generated_Code folder, which is not included in the project. In general, you don’t need to change anything in this code, but you shall know that such folder exists, and it is the location of all RIA Generated code.

 

Now move to the client implementation. We have to implement following steps:

1.       Define an area in our application to drop files

2.       Keep track of all files dropped and display file names

3.       Give end users option to remove a file from the list to be uploaded

4.       Give end users option to compress all files in single archive before upload

5.       Give end users a mean to actually upload the files (via a button)

6.       (Optional) Expose a list of all uploaded files to the client

 

How to define the area for dropping files? Since Silverlight 4, FrmaeworkElement has property named “AllowDrop” and “Drop” event, so I basically create a border element in my layout, from which I will accept files:

<Border Grid.Row="1" Grid.Column="0"

Margin="20 20 20 20" BorderBrush="Black" BorderThickness="2"

       AllowDrop="True" Drop="Border_Drop"

       >

 

In the code behind I have defined a property of type ObservableCollection<FileInfo> in which I will keep list of all dropped files, and I will bind this property to a ListBox, so I will be showing the list of files to the end user. The Drop Handler is defined as follows:

        private void Border_Drop(object sender, DragEventArgs e)

        {

            System.Diagnostics.Debug.WriteLine(e.Data.ToString());

            FileInfo[] fi = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];

            if (fi != null)

            {

                foreach (FileInfo fInfo in fi)

                {

                    AddFileToUpload(fInfo);

                }

            }

        }

Helper method AddFileToUpload(FileInfo file) is used to make check if a file with same name and same size already exists in the collection. I mentioned earlier that our Key is the file name, and we can’t handle files with same names for current implementation.

 

Once we have the list of files to upload, we have to upload them. Here is the implementation:

        private void btnUpload_Click(object sender, RoutedEventArgs e)

        {

            FileUploaderDomainContext ctx = new FileUploaderDomainContext();

            int i = 0;

            ZipFile zFile = new ZipFile();

            bool Compress = (chkCompress.IsChecked.HasValue && chkCompress.IsChecked.Value);                

            foreach (FileInfo fi in this.FilesToUpload)

            {

 

                if (Compress)

                {

                    ZipEntry entry = ZipEntry.CreateFile(fi.Name, "", FileInfoHelper.GetFileBytes(fi));

                    zFile.Entries.Add(entry);

                }

                else

                {

                    WebFile file = new WebFile { IsCompressed = false, FileName = fi.Name, FileContent = FileInfoHelper.GetFileBytes(fi) };

                    ctx.WebFiles.Add(file);

                }

                i++;

            }

            this.progressBar.IsEnabled = true;

            this.progressBar.Visibility = System.Windows.Visibility.Visible;

            if (Compress)

            {

                using (MemoryStream ms = new MemoryStream())

                {

                    zFile.Save(ms);

                    WebFile file = new WebFile();

                    file.FileContent = ms.ToArray();

                    file.IsCompressed = true;

                    file.FileName = this.tbFileName.Text;

                    ctx.WebFiles.Add(file);

                }

            }

            ctx.SubmitChanges(SubmitCallBack, null);

        }

 

The usage of Compression Library is very simple. We first need to create an instance of ZipFile:

        ZipFile zFile = new ZipFile();

And then fill it with ZipEntries:

        ZipEntry entry = ZipEntry.CreateFile(

             fi.Name, "", FileInfoHelper.GetFileBytes(fi));

        zFile.Entries.Add(entry);

When we are ready with all entries, we just get the compressed bytes from the ZipFile instance:

 

using (MemoryStream ms = new MemoryStream())

{

       zFile.Save(ms);

       WebFile file = new WebFile();

       file.FileContent = ms.ToArray();

       file.IsCompressed = true;

       file.FileName = this.tbFileName.Text;

       ctx.WebFiles.Add(file);

}

 

I use a MemoryStream to get out the Compressed bytes, then create a new instance of WebFile (remember, this is our POCO object that is handled by RIA Service library), mark this object as compressed, and set its name to a name provided by end user.

 

Finally, just call ctx.SubmitChanges(SubmitCallBack, null) on the Domain Context, and it automatically does the asynchronous call and error handling for us!

 

The idea to enable compressed and uncompressed upload is to be able to track differences, and here is a simple demonstration:

 

The first request is without compression, while the second is using Compression.

 

While exploring the sample, you might encounter an error from the server “404 not found”. This will be in the SubmitCallback handler:

        private void SubmitCallBack(SubmitOperation op)

        {

            if (op.HasError)

            {

                MessageBox.Show(op.Error.Message);

                return;

            }

            MessageBox.Show("Success");

            this.progressBar.IsEnabled = false;

            this.progressBar.Visibility = System.Windows.Visibility.Collapsed;

        }

This error will most probably come from the requestLenght limit of the server. By default the ASP.NET request length is 4MB. If you want to upload files larger than 4MB (and when using this sample you will hit this limit very easily) you have to change your web.config file by adding more liberate request length limit (100MB in this case):

<httpRuntime

        maxRequestLength="1048576"

        executionTimeout="180"

          />

Http runtime configuration options are added within system.web section of web.config file.

Again, the full source code for the application, can be downloaded from here.


Comments  (6 )

Jason Beres
on Fri, Apr 30 2010 12:45 PM

Great post.  I needed something like this for a customer question, thanks!

fantazia
on Wed, May 19 2010 6:52 AM

thanks for this code

but i have a problem i can't find the Domain Service class

at the items

do you know a solution for that ????

thanks a lot  

Ram Singh
on Tue, Mar 8 2011 1:31 AM

Thanks you Very Much

I was struggling from 2 Days to Make this Task.

I have been Solved

Smile90
on Mon, May 9 2011 5:37 AM

At first,thanks you very much

I have struggled for a long time to make this task,but I have a problem now, i can't find the namespace"Infragistics.Silverlight.Compression.v10.1"and "Infragistics.Silverlight.v10.1",

Do you kown how to do?

I'm struggling!

Thank you

hasanarik
on Mon, Jan 30 2012 5:22 AM

Can you please also point us towards a sample code on how we can take the uploaded file (stored locally on the web server or on the DB as blobs) and deliver it back to the users using RIA + Compression library. File should be transfered zipped and uncompressed on the user pc.

Many thanks

Ankit Kulshreshtha
on Thu, Aug 23 2012 8:10 AM

Awesome post ,great ,you made my day

Add a Comment

Please Login or Register to add a comment.