Silverlight

индекс
62,31

Silverlight File Upload Progress

Возникла как-то передо мной задача, организовать 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. Если кому-то будет интересно, напишу об этом в следующий раз.
+26
28 августа 2008, 01:40
31

комментарии (48)

+2
no_smoking #
Спасибо за статью очень интересно. Тем более что на русском языке по silverlight мало статей еще а технология очень интересная и на мой взгляд перспективная.
+1
fokko #
Понравилось… пиши еще!
+1
Goganchic #
Интересная статья! Пасиб! Я вот думаю в ближайшем будущем может быть придется заняться вплотную си-шарпом, возможно и веб технологиями с использованием оного, так что надо взять эту статейку на заметку :)
0
dmitriev_dmitry #
Если честно мало что понял (я не программист), но то как написана статья и на какую тему очень приятно. побольше бы таких статей! Спасибо автору.
0
kottt #
Огромный плюс в том, что Silverlight активно расширяет свое влияние и теперь работает даже под линуксом (Moonlight), правда, пока только в Firefox, но это ненадолго. Соответственно, веб-приложения на нем скоро будут доступны пользователям всех основных браузеров на виндоус, маке и лунуксе и сомнений перед выбором этой технологии станет меньше.

Автору спасибо за хорошую статью — добавил в избранное.
НЛО прилетело и опубликовало эту надпись здесь
0
kottt #
Ну а что вы хотели, у вас же Moonlight, а не Silverlight стоит, вот и предлагают поставить. Moonlight позволяет запускать Silverlight-приложения под линуксом, но называется-то он по-другому. Возможно, еще роль играет ранняя версия самого Moonlight — он же еще даже до первой версии не дошел (официальная последняя, вроде, 0.7), а его майкросовтовский отец уже 2b2. Думаю, MS предлагает как раз самую последнюю версию поставить, а всех её стандартов Moonlight еще не поддерживает.
НЛО прилетело и опубликовало эту надпись здесь
0
Atreides07 #
Думаю не стоит торопится с выводами — сколько времени уже существует Flash и сколько времени SL? Еще надо учесть что SL с поддержкой языков .NET только в Beta версии. Но уже в таком «младенческом» возрасте SL очень даже неплохо себя проявляет.
НЛО прилетело и опубликовало эту надпись здесь
+1
hannimed #
silverlight.net/showcase/ список постоянно пополняется.
НЛО прилетело и опубликовало эту надпись здесь
0
mixen #
> Но отсутствие кросс платформенности удручает

Если речь идет о 100%-ной кросс-платформенности, то да — Linux-версия работает, но не полностью.
Однако поддержка различных Windows и Mac OS X есть полностью.
+1
kottt #
Здесь совсем другой случай. Над проектом Mono, частью которого является Moonlight, сейчас работает Novell с подачи и с активной поддержкой MS и идет семимильными шагами. К тому же сам Silverlight еще достаточно молод и еще рано что-то говорить, но сама технология очень перспективная и обладает огромным потенциалом.
+1
serega011 #
Да, Дима, лучше тоже самое но на JS, мне лично интереснее это:)
0
neyronius #
Об этом на JS уже много раз писалось. А здесь поле непаханное…
0
serega011 #
Ниодного нормального решения не помню, везде или флэш или cgi в нагрузку идет.
0
hannimed #
Дык, чисто на JS это не возможно :)
0
serega011 #
Дык, а вот это как раз и хреново :). C PHP тоже как понимаю невозможно.
НЛО прилетело и опубликовало эту надпись здесь
0
hannimed #
Согласитесь, что ActiveX — это уже не чисто JS.
НЛО прилетело и опубликовало эту надпись здесь
+1
joger #
на тему закачивания файлов на сервер:
есть замечательная библиотека флеш+яваскрипт.
флеш для того чтобы можно было выбирать несколько файлов сразу.
апи предоставляется через яваскрипт чтобы реализовывать всякие красивости :)

swfupload.org/
0
hannimed #
Интересно было бы посмотреть на устройство внутри.
+1
joger #
там на сайте можно скачать все исходники и примеры.
–4
mayhem #
флеш можно декомпилировать
0
mayhem #
гг. ну не хотите не нада :)
0
watabou #
не сочтите за холивар, но на flash + php проще выглядит
0
hannimed #
Не сочту, наоборот, хотелось бы глянуть. Здесь, например, от силы 100 строк кода (с коментами).
0
fzn7 #
+1
Atreides07 #
очень подробную документацию можно найти на офиц. сайте Silverlight.net

silverlight.net/learn/learnvideo.aspx? video=69793 — тот же самый uploader вместе с кодом и видеотуториал.

silverlight.net/learn/videocat.aspx? cat=2#HDI2Basics — огромное число роликов начиная как сделать собственное окно — когда не утсановлен SL и создание первого приложения — до вещей как сделать собственные контролы, шаблоны и стили контролов, хоткей, запросы к сервисам и многое другое.

silverlight.net/quickstarts/managed.aspx
msdn.microsoft.com/en-us/library/bb404700(VS.95).aspx — документация по SL

silverlight.net/Community/communitygallery.aspx — множество семплов с исходными кодами — тоже приводится примеры как сделать простейшие игры — до примеров по работе с картами VirtualEarth и т.п.

0
fzn7 #
О связке flash + php я там ничего не нашел ;)
0
watabou #
// Делаем все не во флексе, а во флэше, на сцене у нас лежат 2 компоненты:
// txtStatus - это Label, хотя можно было бы использовать и обычное текстовое поле
// btnBrowse - это Button, хотя можно было использовать вообще любой InteractiveObject

// у кнопки слушаем клик по ней
btnBrowse.addEventListener( MouseEvent.CLICK, onBrowse );

function onBrowse( e:MouseEvent ):void {
	// При клике по кнопке запускаем пользовательский выбор файла через системное окно 
	var fr:FileReference = new FileReference();
	fr.addEventListener( Event.SELECT, onSelect );
	fr.browse( [new FileFilter( "Image files", "*.jpg;*.gif" )] );
}

function onSelect( e:Event ):void {
	var fr:FileReference = FileReference( e.target );
	// После того, как пользователь выбрал файл, мы объекту FileReference вешаем обработчики событий на
	// 2 события - прогресс закачки (показываем сколько загрузилось) и окончание закачки (сообщаем об этом)
	fr.addEventListener( ProgressEvent.PROGRESS, function( e:ProgressEvent ):void {
		txtStatus.text = e.bytesLoaded + "/" + e.bytesTotal;
	} );
	fr.addEventListener( Event.COMPLETE, function( e:Event ):void {
		txtStatus.text = "Success!";
	} );
	fr.upload( new URLRequest( "http://www.mysite.ru/upload.php" ) );
}


Не умею красиво форматировать в комментариях. Написано на 3-м ActionScript-е, но в немного олдскульном стиле, без явного описания класса. PHP-ную часть не хочу приводить, чтобы не позориться — не моя область :), хотя тестовый вариант из 3х строк нормально работает.
0
hannimed #
Отличный пример, спасибо! Еще один вопрос: а при отправке интерфейс флеша не подвисает? Т.е. если будут другие кнопки с обработчиками, то в момент отправки файла, они будут работать?
0
watabou #
Нет, как и все подобные методы в экшн-скрипте, метод upload у объекта FileReference является асинхронным — будут работать обработчики, проигрываться анимация и т.д.
0
hannimed #
Круто! Очень элегантно и действительно просто. Надеюсь Adobe скоро создаст расширения для Visual Studio, чтобы можно было легко работать в привычной (.net разработчикам) среде, используя все средства отладки. Я слышал, они уже над этим работают.
0
serega011 #
Делать им больше нечего:)
0
fzn7 #
Копипаст с готовго кода? =) Это меня файлфильтр насторожил =)
Функции в функциях это зло.
Не обрабатываются ошибки, такие как SecurityError и IOError
0
watabou #
Да нет, фильтр можно было бы вообще не ставить, но загрузка картинок — это самый распространенный случай (в моей практике).
Функция в функции — зло? В этом случае считаю, что так код читается лучше, чем объявлять 2 дополнительные функции по одной строчке каждая, которые нигде больше не вызываются.
Ошибки не обрабатываются, да. Но я же не готовый загрузчик предлагаю, это всего лишь concept proof. Так-то много к чему можно придраться.
0
fzn7 #
Да я все понимаю, но тут-же учебный топик и все должно быть как в школе учили.
В любом случае если кому-то пригодится он будет знать на что стоит обратить внимание.

P.S В файлфильтре png забыли =(
0
olexandr17 #
тем более в хелпе лежит готовый аплоадер уже )))
вот пример — www.olexandr.net/content/upload/
0
AlterEG0 #
скачал, посмотрел, не понравилось. У меня новости Вконтакте съезжают. Хотя, даже если бы не съезжали, все ровно не поменял бы на мою Мозилку :)
PS: скриншот съезда новостей в ИЕ8
0
hannimed #
Топиком ошиблись? Или хабр глючит :)
0
AlterEG0 #
Ой… Очень извиняюсь за свою невнимательность :( 20 вкладок Хабра, наверное промазал случайно
0
dummy #
Несколько комментариев к статье и комментариям :)
1. Silverlight клиент есть не только под виндовс, но и под OS X. Это по сути мини CLR, то есть .NET Framework. Разумеется этого клиента можно было сделать под линукс, но микрософт понятное дело это не сделает.
2. Приведенный код по сути не показывает основную идею реализованную в WCF и которая здесь используется — транспорт «тяжелых» бинарных данных с гарантированной доставкой. Поэтому и chunks c MTOM coding. Эти решения широко применяются для реализации data транспорта в бизнес системах c Web services.
3. Более того, WCF по сути следующая версия довольно старой библиотеки MS WSE 2.0 (web service extensions), которая делала все то же самое в смысле транспорта, но была сама «легче» и работает под .NET 2.0. С помощью нее приходилось делать веб решение подобное download менеджерам (reget), но только на upload. С поддержкой веб кластеров, load balancing, докачек, сохранения статусов недокачанных файлов и гарантированной закачки любого количества гигабайтов через любой коннект (хоть dial up). Решение работает стабильно в течение полугода в боевых условиях.
Если интересно хабро-народу, то могу описать это решение более детально. Если хватит кармы на публикацию есс-но :)
0
hannimed #
В данном примере можно было обойтись и без chunks, передавать напрямую Stream, но тогда пришлось бы уделить больше внимания настройке сервиса, т.к. без проблем передаются только маленькие объёмы данных.
Наконец, можно было вообще обойтись без WCF сервиса, воспользоваться .ashx файлом. Но это не тема данного топика. Я же просил, не придерайтесь к сервису :)
0
hannimed #
Я Вам поднял кармы, интересно почитать ваше решение.
0
no_smoking #
Добавил кармы ждем статьи

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.