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]
publicint 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]
publicint 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 inthis.DataContext.UploadedFileParts
where p.FileID == fileID
&& p.Ordinal == ordinal
select p).FirstOrDefault();
if (foundPart !=null&& overwrite)
{
foundPart.FileDataPart = fileData;
this.DataContext.SubmitChanges();
ret =2;
}
elseif (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]
publicint FinalizeFile(int fileID)
{
// File upload is complete, post all file data to the UploadedFiles
var totalPartSizes = from allParts inthis.DataContext.UploadedFileParts
where allParts.FileID == fileID
select allParts.FileDataPart;
Int64 totalPartSize =0;
foreach (var sizePart in totalPartSizes)
{
totalPartSize += sizePart.Length;
}
var totalAssignedSize = (from fileTest inthis.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 inthis.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;
}
elseif (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 inthis.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 inthis.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 inthis.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.
return0;
}
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
{
publicpartialclass UploadItem : UserControl
{
private List fileBuffer =null;
private FileInfo selectedFile =null;
privateint fileID =0;
privateint sectionCount =0;
private ObservableCollection completedSections =new ObservableCollection();
private UploadRiaContext uploadRiaContext =new UploadRiaContext();
privatebool 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;
}
publicvoid 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);
}
}
}
privatevoid StartFileUpload()
{
byte[] msgBody = fileParts.First().Value.ToArray();
uploadRiaContext.UploadFile(selectedFile.Name, "image", selectedFile.Extension, fileBuffer.Count(), msgBody, StartUploadCompleated, 0);
}
privatevoid StartUploadCompleated(InvokeOperation args)
{
if (args.Value ==0)
{
ResetAll();
this.UploaderTextField.Text ="Error during upload start.";
thrownew NullReferenceException("The file insert failed.");
}
else
{
UpdateProgress();
fileID = args.Value;
completedSections.Add(0);
for (int i =1; i < sectionCount; i++)
{
SendSection(i, false);
}
}
}
privatevoid 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);
}
}
privatevoid SendSectionCompleated(InvokeOperation args)
{
if (args.Value !=0)
{
UpdateProgress();
completedSections.Add((int)args.UserState);
}
else
{
completedSections.Remove((int)args.UserState);
SendSection((int)args.UserState, true);
}
}
privatevoid FinalizeFile()
{
if (completedSections.Count() == sectionCount &&!finalizedFile)
{
finalizedFile =true;
uploadRiaContext.FinalizeFile(fileID, FinalizeFileCompleted, 0);
}
}
privatevoid FinalizeFileCompleted(InvokeOperation args)
{
if (args.Value ==0)
{
UpdateProgress();
UploaderTextField.Text ="Transferred successfully.";
ResetAll();
}
elseif (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);
}
}
privatevoid 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:
privatevoid Button_Click(object sender, RoutedEventArgs e)
{
FileUploader.UploadFile();
}