Возникла как-то передо мной задача, организовать File Upload Progress для платформы ASP.NET. Было перекопано множество технологий и решений, но найти простого не удавалось. Был написан HttpModule, который справлялся со своими задачами, но использовать его было довольно трудно.
И тут я подумал: а как с этой задачей справляется Silverlight? Начал активно искать по данной тематике и увидел, что готовых решений не так и много (точней я вообще их не нашел).
В данном топике я привожу свой пример создания прогресса загрузки файла на сервер, с использованием технологии SilverLight 2b2.


Теперь в нашем распоряжении «солюшн», в котором содержится два проекта. Это ASP.NET веб-сайт и Silverlight приложение.
Код включает в себя два метода: сохранение файла и удаление файла, если таковой имеется. Здесь стоит обратить внимание на то, что сохранение файла происходит кусками (chunks). SaveFile принимает имя файла, длину массива и, собственно, массив байт. Принятое содержимое сохраняется в файл, открытый с модом Append (дописывать). Перед каждой отправкой файла, вызывается функция удаления файла, чтобы не возникло ситуации, когда новый файл дописывается к существующему.
После создания WCF-сервиса, необходимо поменять тип «биндинга», который указан в файле Web.config. Находим строку «wsHttpBinding» и меняем её на «basicHttpBinding». В итоге получится следующий код:

С сервисом пока всё, теперь мы можем использовать его в Silverlight приложении.
Собственно это всё, что нам нужно по части интерфейса. Теперь мы можем углубиться в код, который будет заниматься отправкой файла.
PS. При необходимости можно избавиться от интерфейса Silverlight, делать все вызовы из JavaScript'a. Если кому-то будет интересно, напишу об этом в следующий раз.
И тут я подумал: а как с этой задачей справляется Silverlight? Начал активно искать по данной тематике и увидел, что готовых решений не так и много (точней я вообще их не нашел).
В данном топике я привожу свой пример создания прогресса загрузки файла на сервер, с использованием технологии SilverLight 2b2.

1. Создание Silverlight проекта
Начнём с того, что создадим новый проект типа Silverlight Application (на языке C#). Студия предложит выбрать, как будет «хоститься» наше приложение: либо ASP.NET веб-сайт, либо генерируемая HTML-страница. Больше всего нам подойдёт первый вариант, его и выбираем.
Теперь в нашем распоряжении «солюшн», в котором содержится два проекта. Это ASP.NET веб-сайт и Silverlight приложение.
2. Добавление и настройка WCF сервиса
Для сохранения файлов на сервере будем использовать Windows Communication Foundation (WCF) сервис. Прошу отнестись с пониманием к коду сервиса, хотелось сделать его предельно простым и коротким, поэтому имеем абсолютные пути, пренебрежение String.Format и, возможно, другие помарки (на кавычки уже не обращаем внимания).public class Receiver: IReceiver
{
public void SaveFile(String filename, Int32 dataLength, Byte[] data)
{
FileStream fs = File.Open(@«C:\Temp\» + filename, FileMode.Append);
fs.Write(data, 0, dataLength);
fs.Close();
}
public void DeleteIfExists(String filename)
{
if (File.Exists(@"C:\Temp\" + filename)) File.Delete(@"C:\Temp\" + filename);
}
}
Код включает в себя два метода: сохранение файла и удаление файла, если таковой имеется. Здесь стоит обратить внимание на то, что сохранение файла происходит кусками (chunks). SaveFile принимает имя файла, длину массива и, собственно, массив байт. Принятое содержимое сохраняется в файл, открытый с модом Append (дописывать). Перед каждой отправкой файла, вызывается функция удаления файла, чтобы не возникло ситуации, когда новый файл дописывается к существующему.
После создания WCF-сервиса, необходимо поменять тип «биндинга», который указан в файле Web.config. Находим строку «wsHttpBinding» и меняем её на «basicHttpBinding». В итоге получится следующий код:
<services>Причиной этому служит то, что Silverlight приложения могут работать только с basicHttpBinding «биндингом» (пока-что или так будет всегда – не известно).
<service behaviorConfiguration=«ReceiverBehavior» name=«Receiver»>
<endpoint address="" binding=«basicHttpBinding» contract=«IReceiver»>
<identity>
<dns value=«localhost»/>
</identity>
</endpoint>
<endpoint address=«mex» binding=«mexHttpBinding» contract=«IMetadataExchange»/>
</service>
</services>
3. Подключение WCF сервиса
Чтобы использовать созданный сервис в Silverlight приложении, необходимо добавить ссылку на сервис (Add Service Reference…). В диалоговом окне добавления сервиса, нажав кнопку Discover можно выбрать любой сервис текущего «солюшена». Выбираем наш сервис: Receiver, назовём его ReceiverService.
С сервисом пока всё, теперь мы можем использовать его в Silverlight приложении.
4. XAML код
Откроем файл Page.xaml и внесём туда следующий XAML-код:<UserControl x:Class=«UploadProgress.Page»Данный код описывает StackPanel, в которой лежит кнопка и блок текста. У кнопки, на событие Click повешен обработчик OnBrowseClick.
xmlns=«http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x=«http://schemas.microsoft.com/winfx/2006/xaml»
Width=«400» Height=«30»>
<StackPanel Orientation=«Horizontal»>
<Button x:Name=«btnBrowse» Content=«Browse»
Width=«100» Height=«25» Click=«OnBrowseClick» />
<TextBlock x:Name=«lblProgress»
Text=«Please select file...» Margin=«5» />
</StackPanel>
</UserControl>
Собственно это всё, что нам нужно по части интерфейса. Теперь мы можем углубиться в код, который будет заниматься отправкой файла.
5. Отправка файла
Сначала опишем глобальные переменные класса, которые нам понадобятся при отправке файла:private ReceiverClient _receiver; // наш WCF-сервис, созданный ранееТеперь опишем обработчики событий. При клике на кнопке, первым вызывается обработчик OnBrowseClick.
private String _fileName; // имя загружаемого файла
private Stream _fileData; // файловый поток загружаемого файла
private Int64 _dataLength; // длина передаваемых данных
private Int64 _dataSent; // длина отправленных данных
private void OnBrowseClick(Object sender, RoutedEventArgs e)В результате выполнения функции удаления, вызывается обработчик OnDeleteIfExistsCompleted:
{
var dlg = new OpenFileDialog(); // Диалоговое окно выбора файлов
dlg.Multiselect = false; // Запрещаем множественный выбор файлов
dlg.Filter = «All Files|*.*»; // Устанавливаем фильтр на файлы
if ((Boolean)dlg.ShowDialog()) // показываем диалог выбора файлов
{
_fileName = dlg.SelectedFile.Name; // имя загружаемого файла
_fileData = dlg.SelectedFile.OpenRead();// файловый поток
_dataLength = _fileData.Length; // длина передаваемых данных
_dataSent = 0; // длина переданных данных
_receiver = new ReceiverClient(); // Receiver (web-service)
// событие удаления файла
_receiver.DeleteIfExistsCompleted += OnDeleteIfExistsCompleted;
// событие сохранения порции данных
_receiver.SaveFileCompleted += OnSaveFileCompleted;
// вызываем функцию удаления файла,
// на тот случай, если файл с таким именем уже есть
// в результате работы будет вызван обработчик
// OnDeleteIfExistsCompleted
_receiver.DeleteIfExistsAsync(_fileName);
}
}
private void OnDeleteIfExistsCompleted(Object sender, AsyncCompletedEventArgs e)Данная функция ничего не делает, кроме того, что вызывает обработчик OnSaveFileCompleted. Обработчик OnSaveFileCompleted читает порцию данных из файлового потока и вызывает функцию отправки SaveFileAsync, в результате выполнения которой вызывается этот же обрабтчик OnSaveFileCompleted. Рекурсия продолжается до тех пор, пока весь файл не будет передан, после чего вызывается метод OnUploadCompleted.
{
OnSaveFileCompleted(sender, e);
}
private void OnSaveFileCompleted(Object sender, AsyncCompletedEventArgs e)Длина в 4 * 4096 байт — выбрана мной произвольно. Возможно есть более оптимальная длина, но пока так. И последнее.
{
Byte[] buffer = new Byte[4 * 4096];
Int32 bytesRead = _fileData.Read(buffer, 0, buffer.Length);
if (bytesRead != 0)
{
_receiver.SaveFileAsync(_fileName, bytesRead, buffer);
_dataSent += bytesRead;
// уведомляем об очередной загрузке порции данных
OnProgressChanged();
}
else
{
// все данные загружены
OnUploadCompleted();
}
}
private void OnProgressChanged()Вот собственно и всё. Скачать исходники для VS2008 SP1 и Silverlight b2b можно тут.
{
// отображаем текущий прогресс загрузки файла
lblProgress.Text = String.Format("{0} / {1}", _dataSent, _dataLength);
}
private void OnUploadCompleted()
{
// информируем об окончании загрузки
lblProgress.Text = «Complete!»;
}
PS. При необходимости можно избавиться от интерфейса Silverlight, делать все вызовы из JavaScript'a. Если кому-то будет интересно, напишу об этом в следующий раз.