Uploading files from Silverlight client using WCF RIA Services
This article covers the topic of uploading files from a client to server (in a WCF RIA Services enabled Silverlight application). In this example it is described how to upload files in ‘chuncks’ and how to store them in a SQL database. Also for good measure I have added a progress bar seeing that that is kind of obvious thing to have on any site that enables uploads
This articles builds on a similar article by axshon, the article can be found here. What I have done is to tweak the application to work with WCF RIA Services, also I have added a progressbar as mentioned.
In this article I am assuming that you have the following things covered before beginning:
- You have created a Silverlight Application project with WCF RIA Services enabled.
- You have a valid Data Connection to a SQL server that can be viewed through the server explorer.
The first thing you will need to do is to create two tables in the database which will contain the file information and data. The design of the tow tables is shown below, also the FileID in the UploadedFile talble is an identity(1,1).

With the database setup, you need map the relational objects in the database, in my example I am using LINQ to SQL. Assuming you are using the same you need to add a new item to the SilverlightApplication.Web project and select “LINQ to SQL Classes” and name the file whatever you find relevant – in this example it is called DataClasses.
You now need to add the two created tables to your DataClasses file, this is done by opening the DataClasses file and hereafter you navigate to the Server Explorer and drag the two tables to the DataClasses file – after which you save the file and rebuild the SilverlightApplication.Web project.
Now you need to create a new DomainService in the SilverlightApplication.Web project as shown in the image below, in this example I have called the domain service UploadRiaService.

With the domain service created you paste the following code into the domain service:
[Invoke]
public int UploadFile(string fileName, string fileType, string fileExtension, Int64 fileSize, byte[] firstFileData)
{
int ret = 0;
UploadedFile file = new UploadedFile();
file.FileName = fileName;
file.FileType = fileType;
file.FileExtension = fileExtension;
file.FileSize = fileSize;
this.DataContext.UploadedFiles.InsertOnSubmit(file);
this.DataContext.SubmitChanges();
UploadedFilePart part = new UploadedFilePart();
part.FileID = file.FileID;
part.Ordinal = 0;
part.FileDataPart = firstFileData;
this.DataContext.UploadedFileParts.InsertOnSubmit(part);
this.DataContext.SubmitChanges();
ret = file.FileID;
return ret;
}
[Invoke]
public int UploadFilePart(int fileID, int ordinal, bool overwrite, byte[] fileData)
{
// return values:
// 0 = Not inserted
// 1 = Already exists
// 2 = Inserted or updated
int ret = 0;
// Check to be sure this part does not already exist.
var foundPart = (from p in this.DataContext.UploadedFileParts
where p.FileID == fileID
&& p.Ordinal == ordinal
select p).FirstOrDefault();
if (foundPart != null && overwrite)
{
foundPart.FileDataPart = fileData;
this.DataContext.SubmitChanges();
ret = 2;
}
else if (foundPart != null) // should not overwrite
{
ret = 1;
}
else // foundPart == null so ignore overwrite
{
UploadedFilePart nextPart = new UploadedFilePart();
nextPart.FileID = fileID;
nextPart.Ordinal = ordinal;
nextPart.FileDataPart = fileData;
this.DataContext.UploadedFileParts.InsertOnSubmit(nextPart);
this.DataContext.SubmitChanges();
ret = 2;
}
return ret;
}
[Invoke]
public int FinalizeFile(int fileID)
{
// File upload is complete, post all file data to the UploadedFiles
var totalPartSizes = from allParts in this.DataContext.UploadedFileParts
where allParts.FileID == fileID
select allParts.FileDataPart;
Int64 totalPartSize = 0;
foreach (var sizePart in totalPartSizes)
{
totalPartSize += sizePart.Length;
}
var totalAssignedSize = (from fileTest in this.DataContext.UploadedFiles
where fileTest.FileID == fileID
select fileTest.FileSize).First();
if (totalAssignedSize > totalPartSize)
{
// The sizes do not match - Find the first part that does not match the assigned size.
var missingParts = from p in this.DataContext.UploadedFileParts
where p.FileID == fileID
orderby p.Ordinal
select p;
int iTestOrdinal = 0;
foreach (var testPart in missingParts)
{
// Test for contiguous elements
if (testPart.Ordinal != iTestOrdinal)
{
return iTestOrdinal;
}
// Test for size of the element as long as it's not the last one.
if (iTestOrdinal != (missingParts.Count() - 1))
{
if (testPart.FileDataPart.Length != 8000)
{
return iTestOrdinal;
}
}
iTestOrdinal++;
}
// We didn't find the problem. Nothing to do but fail.
return -1;
}
else if (totalAssignedSize < totalPartSize)
{
// There are too many parts.
// Not much we can do here except fail.
return -1;
}
// The total size of parts is the same as the
List allFileBytes = new List();
// Get the list of parts for this item
var parts = from p in this.DataContext.UploadedFileParts
where p.FileID == fileID
orderby p.Ordinal
select p;
foreach (var part in parts)
{
allFileBytes.AddRange(part.FileDataPart.ToArray().ToList());
}
var file = (from f in this.DataContext.UploadedFiles
where f.FileID == fileID
select f).FirstOrDefault();
if (file != null)
{
file.FileData = allFileBytes.ToArray();
}
this.DataContext.SubmitChanges();
// Final test to be sure that the file updated.
var finalTest = (from f in this.DataContext.UploadedFiles
where f.FileID == fileID
select new { f.FileSize, f.FileData }).First();
if (finalTest.FileSize != finalTest.FileData.Length)
{
return -1;
}
// Matching sizes detected. Go ahead and delete the parts.
foreach (var part in parts)
{
this.DataContext.UploadedFileParts.DeleteOnSubmit(part);
}
this.DataContext.SubmitChanges();
// We return zero because the first file part
// was guaranteed by the fact that we received
// a fileID in the very first call to the service.
return 0;
}
Now with the SilverlightApplication.Web project ready need to go to the SilverlightApplication project and add a new file, name this file UploadItem and paste the following code into the .xaml file:
<UserControl x:Class="SilverlightApplication.UploadItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="UploaderTextField" VerticalAlignment="Center" Padding="5"/>
<ProgressBar Grid.Row="1" x:Name="TotalProgress" Value="{Binding Percentage}" Maximum="1"/>
<TextBlock Grid.Row="1" x:Name="PercentLabel" FontSize="12" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" Text="{Binding Path=Percentage, StringFormat=P0}" Padding="0" />
</Grid>
</UserControl>
I UploadItem.xaml.cs tilføjer indsætter du følgende kode:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Windows;
using System.Windows.Controls;
using SilverlightApplication.Web;
namespace SilverlightApplication
{
public partial class UploadItem : UserControl
{
private List fileBuffer = null;
private FileInfo selectedFile = null;
private int fileID = 0;
private int sectionCount = 0;
private ObservableCollection completedSections = new ObservableCollection();
private UploadRiaContext uploadRiaContext = new UploadRiaContext();
private bool finalizedFile = false;
private Dictionary> fileParts;
public UploadItem()
{
InitializeComponent();
completedSections.CollectionChanged += (sender, e) => FinalizeFile();
}
void UpdateProgress()
{
PercentLabel.Text = (Math.Round((decimal)(100.00 / sectionCount) * this.completedSections.Count)) + " %";
this.TotalProgress.Value = this.completedSections.Count;
}
public void UploadFile()
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "All files|*.*";
openFileDialog.Multiselect = false;
if (openFileDialog.ShowDialog() == true)
{
try
{
using (FileStream strm = openFileDialog.File.OpenRead())
{
selectedFile = openFileDialog.File;
using (BinaryReader rdr = new BinaryReader(strm))
{
fileBuffer = rdr.ReadBytes((int)strm.Length).ToList();
}
}
fileParts = new Dictionary>();
var fileSections = from idx in Enumerable.Range(0, fileBuffer.Count()) group fileBuffer[idx] by idx / 8000;
sectionCount = fileSections.Count();
int ordinal = 0;
foreach (var section in fileSections)
{
List itm = new List();
foreach (var b in section)
{
itm.Add(b);
}
fileParts.Add(ordinal, itm);
ordinal++;
}
this.TotalProgress.Minimum = 0;
this.TotalProgress.Maximum = sectionCount;
PercentLabel.Text = "0 %";
StartFileUpload();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
private void StartFileUpload()
{
byte[] msgBody = fileParts.First().Value.ToArray();
uploadRiaContext.UploadFile(selectedFile.Name, "image", selectedFile.Extension, fileBuffer.Count(), msgBody, StartUploadCompleated, 0);
}
private void StartUploadCompleated(InvokeOperation args)
{
if (args.Value == 0)
{
ResetAll();
this.UploaderTextField.Text = "Error during upload start.";
throw new NullReferenceException("The file insert failed.");
}
else
{
UpdateProgress();
fileID = args.Value;
completedSections.Add(0);
for (int i = 1; i < sectionCount; i++)
{
SendSection(i, false);
}
}
}
private void SendSection(int sectionKey, bool overWrite)
{
List foundPart;
if (fileParts.TryGetValue(sectionKey, out foundPart))
{
byte[] msgBody = foundPart.ToArray();
uploadRiaContext.UploadFilePart(fileID, sectionKey, overWrite, msgBody, SendSectionCompleated, sectionKey);
}
}
private void SendSectionCompleated(InvokeOperation args)
{
if (args.Value != 0)
{
UpdateProgress();
completedSections.Add((int)args.UserState);
}
else
{
completedSections.Remove((int)args.UserState);
SendSection((int)args.UserState, true);
}
}
private void FinalizeFile()
{
if (completedSections.Count() == sectionCount && !finalizedFile)
{
finalizedFile = true;
uploadRiaContext.FinalizeFile(fileID, FinalizeFileCompleted, 0);
}
}
private void FinalizeFileCompleted(InvokeOperation args)
{
if (args.Value == 0)
{
UpdateProgress();
UploaderTextField.Text = "Transferred successfully.";
ResetAll();
}
else if (args.Value == -1)
{
UploaderTextField.Text = "Upload failed. Contact the system admin.";
}
else
{
completedSections.Remove(args.Value);
finalizedFile = false;
MessageBox.Show("Failure detected is at ordinal position " + args.Value.ToString() + ". Retrying this file section.");
SendSection(args.Value, true);
}
}
private void ResetAll()
{
fileBuffer = null;
selectedFile = null;
fileID = 0;
completedSections.Clear();
finalizedFile = false;
fileParts = null;
}
}
}
Finally to test the uploader you can add the following the UploadItem to any xaml file, I added it to the MainPage.xaml like this:
<SilverlightFileUpload:UploadItem x:Name="FileUploader" Grid.Row="1" ></SilverlightFileUpload:UploadItem> <Button Content="Upload file" HorizontalAlignment="Right" Width="100" x:Name="UploadButton" Grid.Row="2" Click="Button_Click" />
The button click just starts the Upload Process:
private void Button_Click(object sender, RoutedEventArgs e)
{
FileUploader.UploadFile();
}
Thats all folks…
|
|








October 6th, 2010 at 19:56
Simple yet the ideas are presented in a wonderful way. I just like the way on how you write this blog post!
October 21st, 2010 at 06:48
Why we have to split the data in chunks of 8000 before uploading?
one more question, when i download the same file from database, can i get the complete data ffrom fileuploader table for single document?
October 21st, 2010 at 12:14
Hi Prakash
The chunksize of 8000 is the maximum valid length of the varbinary DataType (as you can refer to here). So you can only make the value smaller if you perfer smaller chunks – at least while we are using the varbinary DataType.
As for your second question, I do not see any problem in downloading the whole file in a single document – though that would mean that you have to get it all from the database and assemble it in memory on the server. But as for downloading it you can try to look into the WebClient class – though to be honest I have not made a downloader to test it myself.
Best Regards
/Peter
October 25th, 2010 at 14:07
Hi peter thanks for your reply
I tested the downloading the part.its working fine but i tried like this
1. we are joining all the parts and updating a single record in upload file table
2. get the value from Filedata column and convert it back to same file.
3. This is working absolutely fine.
My Question: Do you see any performance issue in above scenario?
October 25th, 2010 at 14:12
varbinary(MAX) is the data type we are using, so i am not seeing any limit of 8000 here.
am i missing something here?
October 27th, 2010 at 09:42
Hej Prakash
As for the performance on the server side, I would think that if you are assembling huge (or many farly large sized) files and serving them in a single call, then it would hog a lot of ressources on the server – or worse
. As I understand your post you are taking all the fileparts from the DB and assembling them in memory – if you have many simultanious requests, or big files that would require large amounts of memory. Also for big files it would be nice with the option to pause/resume the download – which you can implement easy enough with chunks.
So IF you are working with large files and you are serving it in a single call (and assembling the entire file from the database – in memory) then I would not recommend it. Just for the info then streaming is a good alternative to chunking, but here you would have to be able to open a stream to the file – and there is some constrictions to the bindings of the service.
As for the varbinary(MAX) – you are not overlooking anything
you must be using a newer version of MS SQL Server which supports way bigger varbinary values than the older SQL Server versions (as you can see the link is for a MS SQL Server 2000).
Best regards
/Peter
December 30th, 2010 at 07:15
Hi ,with out database how to do suppose i am creating one folder in server side of the application itself ,where i will save all the uploaded files there
January 21st, 2011 at 09:57
Hi Balaji
If you would like to store the files on the file system, which in many scenarios is the best way to go, I suppose you can create the file in the UploadFile method and append to it from there… If you store the files on the file system you can settle with storing the file location and other relevant information in the database.
Best regards
/Peter
January 24th, 2011 at 11:54
Hi Peter!!!
Many thanks for your post!!! It helped me a lot!!!
Can you explain me how download file from WCF RIA to Silverlight with a progress bar?
DomainService method:
[Invoke]
public byte[] LoadFile(int attachmentID)
{
List bytes = new List();
var query = (from attachment in DataContext.Attachments
where attachment.AttachmentID == attachmentID
select attachment).FirstOrDefault();
if (query == null || query.Data == null)
return bytes.ToArray();
return query.Data.ToArray();
}
Silverlight code:
private void button1_Click(object sender, RoutedEventArgs e)
{
_saveDialog = new SaveFileDialog();
bool? result = _saveDialog.ShowDialog();
if (result == true)
_dbDomainContext.LoadFile(40, UploadCompleted, null);
}
private void UploadCompleted(InvokeOperation args)
{
byte[] file = (byte[]) args.Value;
_saveFileStream = _saveDialog.OpenFile();
StreamWriter sw = new StreamWriter(_saveFileStream);
sw.BaseStream.Write(file, 0, file.Length);
sw.Flush();
sw.Close();
}
This is working fine but I want to add progress bar to show downloading progress.
How can I determine the percentage of loading?
March 4th, 2011 at 17:27
Trying to use you example but when I paste the web service code into Uploader.svc I get errors on all the line refering to FileUploadTestEntities. Added the connection that refers to FileUploadEntities from my DBContext library into the web config. Any idea as to what I’m doing wrong?
April 1st, 2011 at 21:07
any chance some one can convert this to vb.net,
var fileSections = from idx in Enumerable.Range(0, fileBuffer.Count()) group fileBuffer[idx] by idx / 8000;
April 4th, 2011 at 18:42
Hi Josh
You can try http://www.developerfusion.com/tools/convert/csharp-to-vb/
I am not a big vb user so I hope that page will do it for you?
Best Regards
/Peter
May 2nd, 2011 at 12:17
hi, Peter!
Many thanks for your post! … In my opinion, I’m found a bug, when I’m trying to upload second file, after first successfully loading – progress loading second file is stopped (for example, it may be 60% or 80% of progress bar). I’m already trying 3-4 once. Why is this happening?
May 23rd, 2011 at 10:07
Hi DamiR
It sounds like a problem with resetting after first upload then, are you trying to upload files simultaniously or do you wait for the first to finish before you start the next? Could you post your code and I can have a look…
Best Regards
/Peter
May 23rd, 2011 at 15:13
Hi Peter, I’m waiting for the first to finish before start the next upload. and code don’t changed (i’m took from your sample code), for exception, I’m only pasted own checks, but they don’t affect your code. And I have problem with resetting after first upload, because i can’t to load other files. Progress loading second,third,… file is always not complete (from ~60% to ~90% of progress bar). I’m checked all your resettings in sample, but i can’t find a problem. May be you try to upload a few files
What do you think ?
May 24th, 2011 at 09:35
Hi DamiR
Thanks for your post, I have tested it and you are right when you mention that it must be a “bug” – sorry that I have not tested it properly
I have now corrected the example above, the problem was that in the ResetAll() method in the line:();
completedSections = new ObservableCollection
In that line the Collection is released and a new one is created, the problem with this is that the event listener that has been added in the constructor is also lost, and since we do not add a new event handler – the FinalizeFile() method will only be called for the first file uploaded. Therefore the line in the ResetAll() has been changed into:
completedSections.Clear();
In this way the collection is just emptied and is not thrown away for the GC to eat up… Alternatively you can stick with the original solution but then you have to add the event listener again as it is done in the constructor – though I don’t know why anyone would choose that solution
.
Please don’t hesitate if you should have any further questions.
Best Regards
/Peter
May 24th, 2011 at 13:29
excellent
it’s worked! I have yet one question, how can I to change source code for uploading immediately a few files? i.e. if property Multiselect of OpenFileDialog to set in true;
May 24th, 2011 at 14:44
Good to hear
As for the multi upload – the code is not structured for multiupload as such, but I see no problem in using multiselect in the Dialog and then uploading all the files one after one, you will obviously need to change the code here and there though…
I would start by making a “upload object” to hold each item to upload (the FileInfo, parts, counter and such) and then modify the progressbar to take all the objects to upload into account. Then you could “iterate (not really with callbacks – but you know what i mean
)” through the upload objects – uploading them one by one.
In this way the upload would still be one item at the time, but at least the user can select a handfull of items – i guess thats the same way as fx. facebook does it, at least it looks that way
Best Regards
/Peter
May 25th, 2011 at 09:34
Thx for answer! I’ll to try to do this. =)
June 7th, 2011 at 03:26
Hi,
You have a full source code?
June 7th, 2011 at 08:22
Hi Senthamil
Due to the fact that you need a database to upload to, it would be easier to copy the code above since you need to create your own domain services based on the your own datacontext.
If you on the other hand have soome concrete problems with the code, I would be happy to attempt to assist you
Best Regards
/Peter
July 12th, 2011 at 01:13
Hi Peter,
After several failed attempts at doing my own thing your blog came to my rescue. I only have one problem though that relatively big files causes an exception:
“Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.”
any idea how to get arround this. I tried to simply resend the part it failed on but the error remains.
Thanks again for a great article.
Jan
July 13th, 2011 at 08:12
Hi Jan
Well the issue seems to be with the sql server, which sql server do you use, and what version? Also this problem only occur with big files you mention – what size are we talking? Im not that big in sql servers, but the default deadlock priority could be lifted with the SET DEADLOCK_PRIORITY { LOW | NORMAL | HIGH } though that seems a bit more like a hack than a fix to me
Best Regards
/Peter
September 19th, 2011 at 16:41
Hello Peter,
How can you make InsertOnSubmit(file) and SubmitChanges() calls work? no matter how much I tried , I can´t make it work. To make it work, It tells me to add “System.Servicemodel.DomainServices.Client ” reference, but I can´t do that on the .Web project, only in the silverlight (client side) project.
I´m using silverligth 5 RC, can this be the problem?
How have you solved this?
September 20th, 2011 at 08:15
Hi Ibon
The example is made in Silverlight 4, and is not tested wit SL 5 – so if that has any influence I cannot say. It seems you are missing an assembly, have you added references to Microsoft.ServiceModel.DomainServices.LinqToSql? As that is what I am using in this sample… Otherwise you are welcome to zip you code and mail it to me, and i can have a look – you can find my mail under contacts
Best Regards
/Peter
September 21st, 2011 at 16:19
Hello again Peter,
I have found the error. I was using the ADO entity data model, so I couldn’t make submit changes. So I started a new project creating the LINQ SQL Classes. But know I have another problem. After adding the Linq SQL Classes, rebuilding all (several times), I try to add the Domain Context but I can´t see the entity context, so I can´t add the two new entities to the Domain Service. How can I change that?
Thanks for all,
Ibon
November 9th, 2011 at 20:00
Peter,
Thank you for this! I need this functionality on a project I am currently working. The code doesn’t compile as listed here, but I will work through those issues on my own.
I do have a question though: while this is great for uploading files in chunks via RIA services to a database, what would you do if a user needed to download files already in the database?
November 10th, 2011 at 09:34
Hi Stacy
I hope you will get it working
As for downloading, I never made a chunked downlaoder. Howevere there is a sample here: http://www.codeproject.com/KB/silverlight/DownloadingInChunks.aspx
I myself use HttpHandlers for downloading – if you wish to look into this there is some info here: http://dodgethecode.blogspot.com/2008/09/large-file-download-using-httphandler.html
Good luck on the project
Best Regards
/Peter