This article is compatible with the latest version of Silverlight.
Introduction
Some years ago (pre-silverlight times) a client ask me for a weird requirement…he wanted to drag files from his local explorer and drop them on their ASP .net grid application without using any ActiveX help… I thought, umm… is that possible?
With the advent of Silverlight 2 we got the same request again, … we knew this time it was not a technology limitation, it was a sandbox security limitation, how does the Ms chaps can offer this feature without exposing a security hole? Well it seems that they have managed to “open the box” for this feature in Version 4 on a secure and very easy to use way.
In this article we will cover the basics of this functionality, dig into a more complex sample, and give you guidance on some common drag and drop scenarios.
Note: This article is co-authored with Jose Almoguera.
Step 1: The basics
Desktop drag and drop… What are we talking about? Allow Dragging and dropping any file from your desktop or windows explorer to a Silverlight app, just the same way you with desktop applications (e.g. drag from a windows explorer some files and drop it in another one).
Is that hard to implement? Not at all… have we only needed to perform the following steps:
- Choose the elemento where we want to allow the drop (e.g. a stackpanel).
- In that element set to true the flag “AllowDropElement”.
- Hook to the “Drop” event.
- Whenever that event is fired, we will get as parameter the list of files dropped, we only need to iterate through each of the file and process them.
Let’s start with a basic sample, we are going to create a silverlight app that:
- Has an area defined to drop text files.
- Once the user drops a file(s), the content is shown in a multiline textbox.
The only thing you need to define in your XAML is where you want to allow file dropping (in this case in the orange background stack panel), and the method that will be hooked to the file(s) dropped event:
<StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
<!-- AllowDrop on this stack panel, and specify the method to call when the file(s) are dropped -->
<StackPanel Height="150" Margin="5" Background="Orange" AllowDrop="True" Drop="StackPanel_Drop">
<TextBlock Text="Drop text files here from your desktop" HorizontalAlignment="Center" Margin="0,60,0,0" FontSize="18" />
</StackPanel>
<TextBlock Text="Results shown here:"/>
<!-- In this textbox we will show the results -->
<TextBox x:Name="txContent" Height="180" AcceptsReturn="True" VerticalScrollBarVisibility="Auto"/>
</StackPanel>
On the codebehind we will implement the StackPanel_Drop method, here we will receive as a parameter the list of files dropped, and we just only need to iterate through each of the files, read the content and append it to our multiline text box:
/// <summary>
/// This event is called whenever an user drags a file or files from his desktop (or e.g. windows explorer) and drops it
/// into the selected stack panel .
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StackPanel_Drop(object sender, DragEventArgs e)
{
StringBuilder contentBuilder = new StringBuilder();
string content = string.Empty;
// Let's check that the drop contains data
if (e.Data != null)
{
// The user can drop one or more files
FileInfo[] files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
// In our case we expect text files, for the sake of simplicity error handling has been removed
// you should add here try / catch blocks
foreach (FileInfo fi in files)
{
// Read each text file
using (StreamReader sr = fi.OpenText())
{
// Add the content to the stringbuilder container
contentBuilder.Append(sr.ReadToEnd());
contentBuilder.Append("\n");
}
}
}
// Dump all the content to the multiline textbox
txContent.Text = contentBuilder.ToString();
}
Step 2: Silver Sky
Now that we have covered the basics, let’s go a for a more advanced sample, Do you have an Ms Sky Drive account? It offers great file storage on the “cloud”, you just select a set of files and upload them to your private drive space, some time ago they implemented a really useful feature, instead of use the file open dialog, let the users upload files by dragging them from their desktop to this web application (see snapshot below).
Why not implement the same feature using Silverlight? We have called this sample “Silver Sky”:
The scenarios we have implemented:
- Browser for server files: At application startup we read the content of the server file folder and show it in our Silver Sky app.
- Upload Files: this one is the most interesting scenario, user drags from their desktop a selection of files and drop them in the box “Upload New File To Server”, the upload process starts and a progress bar indicates us how is being performed the operation.
- Download file(s): A server file is dragged from the server file list and dropped into the Drop here to download box, the file is downloaded to the user’s local hard disk.
Let’s get our hands dirty and focus on the code that has been implemented to cover the upload case: Drag & Drop mechanism works the same way as in the previous sample (allow drop + drop event, args list FileInfo), the tricky part comes when you want to upload the file to the server, in some cases users will upload big files, and we have to take into account that:
- User Interface thread should not get busy,
- Users should be notified of the progress of the upload process.
We need to perform this upload in an asynchronous way, to do that we have implemented the following flow:
The most interesting parts of this flow implementation are:
Start Uploading file:
/// <summary>
/// Upload the file specified
/// </summary>
/// <param name="file"></param>
public void UploadFile(FileInfo file)
{
File = file;
UploadFileEx();
}
/// <summary>
/// Start a request to upload a chunk of file.
///
/// Once the server is ready the WriteCallback method will be called (here we extract that chunk and
/// add it to the request stream)
/// </summary>
private void UploadFileEx()
{
Status = FileUploadStatus.Uploading;
long temp = FileLength - BytesUploaded;
UriBuilder ub = new UriBuilder(_urlServer);
bool complete = temp <= ChunkSize;
ub.Query = string.Format("{3}filename={0}&StartByte={1}&Complete={2}", File.Name, BytesUploaded, complete, string.IsNullOrEmpty(ub.Query) ? "" : ub.Query.Remove(0, 1) + "&");
HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(ub.Uri);
webrequest.Method = "POST";
// Begins an asynchronous request for a Stream object to use to write data.
// The asynchronous callback method uses the EndGetRequestStream method to return the actual stream.
// This means
webrequest.BeginGetRequestStream(new AsyncCallback(WriteCallback), webrequest);
}
Write the current chunk of file to server, wait for response send the next chunk
/// <summary>
/// Request to write...
/// - Get the current chunk of file to send.
/// - Add it to the requestStream
/// - Send an event to notify listeners that the chunk of file has been sent (e.g. progress bar on the UI)
/// - Send it
/// - Whenever the operation is ready call the ReadCallback method (when this method
/// is called we know that the chunk of file has arrived succesfully to the server,
/// it's time to process the next chunk).
/// </summary>
/// <param name="asynchronousResult"></param>
private void WriteCallback(IAsyncResult asynchronousResult)
{
HttpWebRequest webrequest = (HttpWebRequest)asynchronousResult.AsyncState;
// End the operation.
// Here we obtain the stream to write the request
Stream requestStream = webrequest.EndGetRequestStream(asynchronousResult);
byte[] buffer = new Byte[4096];
int bytesRead = 0;
int tempTotal = 0;
Stream fileStream = File.OpenRead();
// Get the current chunk of file to send.
fileStream.Position = BytesUploaded;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0 && tempTotal + bytesRead < ChunkSize)
{
// Feed the request Stream with the chunk of file we want to send
requestStream.Write(buffer, 0, bytesRead);
requestStream.Flush();
BytesUploaded += bytesRead;
tempTotal += bytesRead;
// Send an event to notify listeners that the chunk of file has been sent (e.g. progress bar on the UI)
if (UploadProgressChanged != null)
{
int percent = (int)(((double)BytesUploaded / (double)FileLength) * 100);
UploadProgressChangedEventArgs args = new UploadProgressChangedEventArgs(percent, bytesRead, BytesUploaded, FileLength, File.Name);
this.Dispatcher.BeginInvoke(delegate()
{
UploadProgressChanged(this, args);
});
}
}
fileStream.Close();
// Chunk of data sent
requestStream.Close();
/// Whenever the operation is ready call the ReadCallback method (when this method
/// is called we know that the chunk of file has arrived succesfully to the server,
/// it's time to process the next chunk).
webrequest.BeginGetResponse(new AsyncCallback(ReadCallback), webrequest);
}
File chunk sent, acknowledge from the server received, let’s send the next chunk (or jump to the next file to upload).
/// <summary>
/// File chunk send to the server and read successfully ?
/// Yes... do we still have chunks of files to send?
/// Yes... let's send them
/// No... Change the FileUploadStatus to Complete (this will fire a check
/// to start the upload of the next file in the qeue)
/// when the the web reponse is recieved we can send a new part of the file
/// </summary>
/// <param name="asynchronousResult"></param>
private void ReadCallback(IAsyncResult asynchronousResult)
{
// We could use this response to handle errors (e.g. corrupted packet or...)
/*
HttpWebRequest webrequest = (HttpWebRequest)asynchronousResult.AsyncState;
HttpWebResponse response = (HttpWebResponse)webrequest.EndGetResponse(asynchronousResult);
StreamReader reader = new StreamReader(response.GetResponseStream());
string responsestring = reader.ReadToEnd();
reader.Close();
*/
// Yes... do we still have chunks of files to send?
if (BytesUploaded < FileLength)
// Yes... let's send them
UploadFileEx();
else
{
// No... Change the FileUploadStatus to Complete (this will fire a check
// to start the upload of the next file in the qeue)
Status = FileUploadStatus.Complete;
}
}
On the server side we use a custom HTTP Handler to process the petitions. You can download the full sample here.
About the sample, it’s more oriented to understand and learn how this type of upload works, for the sake of simplicity it doesn’t contain code to check errors, or validate the uploaded files, in the next section you can find good links about fully implemented file upload applications.
What’s next?
We have checked in this article that enabling desktop file drag and drop it’s quite easy, but once you’ve got the files, comes the hard part, process the content of that files. Here you have some tips for common drag and drop scenarios:
- File Uploading: If you want to further develop this sample, or make your own research, there are some useful links that can help you:
- File Upload in Silverlight : Basics about how to upload a file using SL.
- Simple Silverlight Uploader with Progress Bar using HttpRequest or WCF: complete sample about how to perform an async upload via HTTPRequest or WCF.
- Codeplex Silverlight file upload: Excellent uploading project, supports multi upload, rich error handling, worth to download and play with the code.
- Processing pictures and video: Dragging and dropping multimedia files to our application is great, Silverlight has great support for them, you can for instance: let an user quick build an image catalog, or play a webcast / video by dragging from his desktop.
- Silverlight 4’s new Drag and Drop Support: Simple and useful sample, drop a picture and shows it in a picture catalog.
- Processing Excel files: If you are a LOB app developer, this request will be quite familiar for you… the complicated part of this operation comes when you have to parse / understand the excel content. Reading this type of file is quite harder than generating an output in excel, things that you can try:
- Simple solution… just support CSV format. Reading CSV format is simplifies a lot the parsing scenario, on the other hand users will complain and will ask for real Excel processing, about CSV reading.
- There is an open source project available (ExcelReader) that is relatively easy to port to Silverlight, it’s able to read both binary and xml format. This can be a good starting point, but it would be a good idea to limit the file types supported to 2007 format, and I would get good knowledge of Open XML format and LINQ to XML, just to extend the library if needed and be able to fix any possible bug.
- Another possibility is doing all yourself (pain in the neck?), grab CSharpZLib, unzip the xlsx file and use LINQ To XML to read the file. Something that could help us to reduce the complexity is to make use of Excel Tables (by using this you would separate layout from data, and ensure that the data you are going to read is tabular).
- If you have budget, something to check are third parties components (e.g. infragistics, ….), a good practice is to download the trials of that products and check if it really fits your needs, then decide whether to purchase it or not.
- If you are application is going to be used by very few users and all that user are going to have the same environment, you can try the new SL 4 com interop.Your app will need to ask for elevated permissions, and it can be as well something prone to issues (e.g. user changes Excel version, or removes it… your application relies on the software installed on a client machine).
For sure you will have new exciting ideas, let’s use the comments section to further discuss them.