Pull to refresh

Silverlight File Upload Progress

Reading time6 min
Views3.5K
Возникла как-то передо мной задача, организовать File Upload Progress для платформы ASP.NET. Было перекопано множество технологий и решений, но найти простого не удавалось. Был написан HttpModule, который справлялся со своими задачами, но использовать его было довольно трудно.

И тут я подумал: а как с этой задачей справляется 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>
   <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>
Причиной этому служит то, что Silverlight приложения могут работать только с basicHttpBinding «биндингом» (пока-что или так будет всегда – не известно).

3. Подключение WCF сервиса

Чтобы использовать созданный сервис в Silverlight приложении, необходимо добавить ссылку на сервис (Add Service Reference…). В диалоговом окне добавления сервиса, нажав кнопку Discover можно выбрать любой сервис текущего «солюшена». Выбираем наш сервис: Receiver, назовём его ReceiverService.


С сервисом пока всё, теперь мы можем использовать его в Silverlight приложении.

4. XAML код

Откроем файл Page.xaml и внесём туда следующий XAML-код:
<UserControl x:Class=«UploadProgress.Page»
  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>
Данный код описывает StackPanel, в которой лежит кнопка и блок текста. У кнопки, на событие Click повешен обработчик OnBrowseClick.

Собственно это всё, что нам нужно по части интерфейса. Теперь мы можем углубиться в код, который будет заниматься отправкой файла.

5. Отправка файла

Сначала опишем глобальные переменные класса, которые нам понадобятся при отправке файла:
private ReceiverClient _receiver;   // наш WCF-сервис, созданный ранее
private String _fileName;      // имя загружаемого файла
private Stream _fileData;      // файловый поток загружаемого файла
private Int64 _dataLength;      // длина передаваемых данных
private Int64 _dataSent;      // длина отправленных данных
Теперь опишем обработчики событий. При клике на кнопке, первым вызывается обработчик OnBrowseClick.
private void OnBrowseClick(Object sender, RoutedEventArgs e)
{
   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);
   }
}
В результате выполнения функции удаления, вызывается обработчик OnDeleteIfExistsCompleted:
private void OnDeleteIfExistsCompleted(Object sender, AsyncCompletedEventArgs e)
{
   OnSaveFileCompleted(sender, e);
}
Данная функция ничего не делает, кроме того, что вызывает обработчик OnSaveFileCompleted. Обработчик OnSaveFileCompleted читает порцию данных из файлового потока и вызывает функцию отправки SaveFileAsync, в результате выполнения которой вызывается этот же обрабтчик OnSaveFileCompleted. Рекурсия продолжается до тех пор, пока весь файл не будет передан, после чего вызывается метод OnUploadCompleted.
private void OnSaveFileCompleted(Object sender, AsyncCompletedEventArgs e)
{
   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();
   }
}
Длина в 4 * 4096 байт — выбрана мной произвольно. Возможно есть более оптимальная длина, но пока так. И последнее.
private void OnProgressChanged()
{
   // отображаем текущий прогресс загрузки файла
   lblProgress.Text = String.Format("{0} / {1}", _dataSent, _dataLength);
}

private void OnUploadCompleted()
{
   // информируем об окончании загрузки
   lblProgress.Text = «Complete!»;
}
Вот собственно и всё. Скачать исходники для VS2008 SP1 и Silverlight b2b можно тут.

PS. При необходимости можно избавиться от интерфейса Silverlight, делать все вызовы из JavaScript'a. Если кому-то будет интересно, напишу об этом в следующий раз.
Tags:
Hubs:
+26
Comments48

Articles

Change theme settings