Главная » Silverlight » Диалоговые окна доступа к файлам

0

Как вы уже знаете, код Silverlight не имеет доступа к файловой системе. Однако с помощью классов OpenFileDialog и SaveFileDialog приложение может читать и запи­сывать отдельные файлы с разрешения пользователя.

Объекты OpenFileDialog и SaveFileDialog выводят на экран стандартные диало­говые окна Open (Открыть) и Save (Сохранить) в ответ на действие, инициированное пользователем (например, щелчок на кнопке). Пользователь должен выбрать файл в диалоговом окне. Полученный файл используется в коде как поток (stream). Диалоговое окно Open создает поток, доступный только для чтения, а окно Save — поток, доступный для записи. В любом случае классы OpenFileDialog и SaveFileDialog предоставляют доступ только к одному файлу.

Примечание. Из соображений безопасности Silverlight не поддерживает классы OpenFileDialog и SaveFile­Dialog в полноэкранном режиме. По умолчанию надстройка Silverlight при обращении к любому из этих классов переключает приложение из полноэкранного режима в обычный, однако лучше переключить приложение явно в коде. Для этого присвойте свойству Application. Current. Host. Content. IsFullScreen значение false, чтобы исключить появление проблем при использовании разных браузеров и операционных систем.

Чтение локальных файлов с помощью класса OpenFileDialog

Объект OpenFileDialog позволяет активизировать традиционное диалоговое окно Open (Открыть). С его помощью пользователь выбирает нужный файл, который стано­вится доступным в режиме чтения. Никаких ограничений на тип файла нет, пользова­тель может выбрать любой файл. Однако доступ к файлу возможен только после того, как пользователь явно выберет его и щелкнет на кнопке Open. Обойти данную операцию программно невозможно. Это условие считается барьером, достаточным для обеспече­ния высокой безопасности.

Для применения класса OpenFileDialog необходимо создать его экземпляр и уста­новить свойства Filter и Filterlndex, определяющие типы файлов, которые пользова­тель увидит в окне. Свойство Filter задает типы файлов, приведенные в списке типов.

Необходимо задать текст, который будет выведен в списке типов, и регулярное выраже­ние, определяющее фильтрацию файлов. Например, если нужно позволить открывать тек­стовые файлы, рекомендуется ввести текст Текстовые файлы (*.txt) и регулярное выра­жение фильтрации *. txt. Тогда в списке будут приведены все файлы с расширением . txt.

OpenFileDialog dialog = new OpenFileDialog();

dialog.Filter = "Текстовые файлы (*.txt)I*.txt";

Символ ! используется для отделения текста от выражения фильтрации. Если нужно предоставить доступ к нескольким типам файлов, необходимо последовательно разме­стить их в строке, отделяя друг от друга символом |.

dialog.Filter =

"Растровые рисунки (*.bmp)|*.bmp|WWW

Файлы JPEG (*.jpg)|*.jpg|WWW

Все файлы (*.*) I*.*";

Чтобы выражение фильтрации определяло несколько типов файлов, их нужно раз­делить символами ;.

dialog.Filter = "Файлы изображений (*.bmp;*.jpg;*.gif)|WWW

*.bmp;*.jpg;*.gif";

Сконфигурировав таким образом объект OpenFileDialog, можно открыть диалоговое окно Open, вызвав метод ShowDialog (). Этот метод возвращает значение DialogResult, сообщающее о том, что сделал пользователь. Если пользователь выбрал файл, возвра­щенное значение равно true и можно открыть выбранный файл.

if (dialog.ShowDialog() == true)

{ … }

Файл предоставляется посредством свойства OpenFileDialog.File типа File- DialogFilelnfo. Класс FileDialogFilelnfo содержит три полезных члена: свойство Name, содержащее имя файла, метод OpenReadO, возвращающий поток FileStream в режиме чтения, и метод OpenText (), создающий поток FileStream и возвращающий объект StreamReader. Ниже приведен пример чтения содержимого файла и записи со­держимого в строку data.

if (dialog.ShowDialog() == true)

{

using (StreamReader reader = dig.File.OpenText ())

{

string data = reader.ReadToEndO ;

Метод OpenText () обычно используется для чтения текстовых данных, а метод OpenRead () — для чтения блоков данных с помощью объекта BinaryReader или метода

FileStream.ReadO .

Совет. Класс OpenFileDialog поддерживает множественное выделение. Для этого перед вызовом метода ShowDialog () присвойте свойству OpenFileDialog.Mulitselect значение true. Все выделенные пользователем файлы будут представлены в свойстве OpenFileDialog. Files.

Файлы, выбранные с помощью класса OpenFileDialog, можно скопировать из ло­кальной файловой системы в изолированное хранилище.

OpenFileDialog dialog = new OpenFileDialog О ; dialog.Filter = "All files (*.*)!*.*"; dialog.Multiselect = true;

// Вывод диалогового окна Open

if (dialog.ShowDialog0 == true) {

// Копирование всех выделенных файлов //в изолированное хранилище using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication ())

{

foreach (FileDialogFilelnfo file in dialog.Files) {

using (Stream fileStream = file.OpenRead()) <

// Проверка свободного пространства

if (fileStream.Length > store.AvailableFreeSpace) {

// Отмена операции копирования // или увеличение квоты

}

using (IsolatedStorageFileStream storeStream = store.CreateFile(file.Name))

{

// Запись блока размером 1 Кбайт byte[] buffer = new byte[1024]; int count = 0;

do

{

count = fileStream.Read(buffer, 0, buffer.Length); if (count > 0)

storeStream.Write (buffer, 0, count); } while (count > 0) ;

)

Запись локальных файлов с помощью класса SaveFileDialog

Как и OpenFileDialog, класс SaveFileDialog предоставляет пользователю возмож­ность просматривать файловую систему компьютера, чтобы выбрать место для сохра­нения файла. Разработчик приложения Silverlight и само приложение не могут получить никакой информации о том, в какой папке сохраняется файл, какие файлы уже есть в этой папке и т.д. Объект SaveFileDialog предоставляет приложению только поток, в который можно писать данные.

Чтобы применить класс SaveFileDialog, нужно создать экземпляр этого класса и (не обязательно) установить фильтр типов. Затем нужно вывести диалоговое окно с помощью метода ShowDialog О и получить поток с помощью метода OpenFile (). Приведенный ниже код демонстрирует копирование текста из текстового поля в задан­ный пользователем файл.

SaveFileDialog saveDialog = new SaveFileDialog ()•;

saveDialog.Filter = "Text Files (*.txt)|*.txt";

if (saveDialog.ShowDialog () == true)

{

using (Stream stream = saveDialog.OpenFile ()) {

using (StreamWriter writer = new StreamWriter(stream)) {

writer.Write(txtData.Text);

}

}

}

Из соображений безопасности, приложение не может установить имя файла, исполь­зуемое по умолчанию в окне SaveFileDialog. Однако с помощью свойства DefaultExt можно установить используемое по умолчанию расширение файла.

saveDialog.DefaultExt = "txt";

Класс SaveFileDialog добавляет заданное расширение к имени файла, введенному пользователем (если пользователь не добавил его сам). Если пользователь ввел другое расширение (например, myfile . test), то расширение, заданное по умолчанию, добав­ляется в конец имени. В результате будет создан файл myfile. test. txt.

Если пользователь выбрал существующий файл или ввел имя существующего файла (возможно, без расширения, которое будет добавлено автоматически), появится пригла­шение подтвердить его замену новым файлом.

После завершения метода ShowDialog (), приложение может извлечь заданное поль­зователем имя файла из свойства SafeFileName, но без имени папки или маршрута.

Обмен файлами с помощью веб-службы

С помощью классов OpenFileDialog и SaveFileDialog можно создать приложение, копирующее файлы с сервера на локальный компьютер или наоборот. Для этого нужна веб-служба, управляющая файлами.

Рассмотрим приложение, показанное на рис. 18.5. При запуске оно запрашивает на сервере список доступных файлов. Получив список, пользователь может выгрузить на сервер свой файл или загрузить с сервера один из предложенных файлов. Несложно создать более сложное приложение, в котором сервер может потребовать аутентифика­ции пользователя или предоставлять разным пользователям доступ к разным коллек­циям файлов.

Рис. 18.5. Обмен файлами с сервером

Поэкспериментируйте с этим приложением с помощью кодов, прилагаемых к данной главе. Далее рассматривается код приложения.

Файловая служба

Пример основан на методе, расположенном на стороне сервера и позволяющем при­ложению Silverlight извлечь список файлов, загрузить с сервера существующий файл в компьютер и выгрузить на сервер файл, расположенный в локальной файловой си­стеме компьютера. В данном примере все эти задачи выполняет служба FileService.

Служба FileService предоставляет клиентам доступ к файлам, расположенным в определенной папке. В данном примере доступные файлы хранятся в папке Files. Ниже приведены контракты методов службы.

[ServiceContract(Namespace = "")]

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class FileService !

private string filePath;

public FileService () (

filePath =

HttpContext.Current.Server.MapPath("Files");

[OperationContract]

public string!] GetFileList()

[OperationContract]

public void UploadFile(string fileName, byte[] data) [OperationContract]

public bytef] DownloadFile (string fileName)

При обработке имен файлов серверный код удаляет информацию о маршрутах, что­бы не было утечки конфиденциальных данных за пределы сервера. Метод GetFileList {) удаляет маршруты с помощью базового класса System. 10. Path.

[OperationContract]

public string[] GetFileList ()

{

// Просмотр папки

string[] files = Directory.GetFiles (filePath);

// Удаление информации о маршрутах

for (int і = 0; і < files.Count(); i++) {

files [і] = Path.GetFileName (files [i]);

}

// Возвращение списка файлов return files;

}

Метод DownloadFile () должен выполнить такую же операцию, но с другой целью. Он удаляет информацию о маршруте из имени файла, предоставленного вызывающим приложением. Это лишает вызывающее приложение возможности передать относи­тельный маршрут типа ../../.. /Windows/System/somef ile. dll, с помощью которого злоумышленники могли бы извлекать секретные файлы.

После удаления маршрута из имени файла, метод DownloadFile () открывает его, ко­пирует его содержимое в байтовый массив и возвращает массив.

[OperationCohtract]

public byte[] DownloadFile(string fileName)

(

// Удаление информации о маршруте string file = Path.Combine(filePath,

Path.GetFileName(fileName)); // Открытие файла, копирование содержимого //в массив и возвращение массива using (FileStream fs = new FileStream(file,

FileMode.Open))

{

bytetl data = new byte[fs.Length]; fs.Read(data, 0, (int)fs.Length); return data;

}

}

Примечание. Метод передачи, используемый в DownloadFile (), требует загрузки содержимого файла в память. Следовательно, этот метод не подходит для больших файлов. Поэтому перед созданием байтового массива рекомендуется проверить длину файла. Если она слишком большая, лучше передать клиенту адрес URI и предоставить ему возможность загрузить файл с помощью URI. Чтобы файлы оставались засекреченными, можно сгенерировать случайное имя файла, содержащее идентификатор GUID (globally unique identifier — глобально уникальный идентификатор), с помощью класса System. Guid.

Служба позволяет клиенту передать блок данных, который будет без проверки запи­сан в папку Files. Пользователь получает только имя файла, без информации о месте, куда он будет записан.

[OperationContract]

public void UploadFile(string fileName, byte[] data)

{

// Удаление информации о маршруте string file = Path.Combine(filePath,

Path.GetFileName(fileName));

using (FileStream fs = new FileStream(file,

FileMode.Create))

{

fs.Write(data, 0, (int)data.Length);

}

}

Проверка длины массива byte [ ] не даст "злоумышленнику записать на диск огром­ный файл, чтобы заполнить диск мусором и нарушить работу сервера. На первый взгляд, тело метода UploadFile () — наиболее подходящее место для проверки. Однако система WCF частично уже позаботилась об этом, ограничив максимальные размеры сообщений и принимаемых массивов. Ограничение введено для предотвращения атак типа "отказ обслуживания". Оно не дает злоумышленнику возможности связать сервер огромными сообщениями и ресурсоемкими процессами.

Следовательно, создавая службу, принимающую большие объемы данных, нуж­но настроить файл web.config на стороне сервера и свойство ServiceReferences. ClientConfig на стороне клиента. Соответствующие конфигурационные параметры в данной книге не рассматриваются. Информацию о них можно получить по адресу www.tinyurl.com/nc8xkn. Можете также просмотреть конфигурационные параметры в кодах, прилагаемых к данной главе. Установленная в них конфигурация позволяет за­гружать и выгружать большие файлы.

Клиентская часть

Клиентский код довольно простой. Все вызовы веб-служб проходят через экземпляр класса FileServiceClient, хранящийся как поле в классе страницы. При первой за­грузке страницы код подключает все обработчики событий завершения. Затем код вы­зывает метод GetFileListAsync (), чтобы передать список файлов объекту списка.

private FileServiceClient client = new FileServiceClient();

private void Page_Loaded(object sender, RoutedEventArgs e)

// Подключение обработчиков событий завершения

// загрузки и выгрузки

client.DownloadFileCompleted +=

client_DownloadFileCompleted; client.UploadFileCompleted +=

client_UploadFileCompleted;

// Получение исходного списка файлов client.GetFileListCompleted +=

client_GetFileListCompleted; client.GetFileListAsync();

}

private void client_GetFileListCompleted(object sender, GetFileListCompletedEventArgs e)

try

IstFiles.ItemsSource = e.Result;

}

catch • {

lblStatus.Text = "Ошибка вызова веб-службы.";

}

}

Когда пользователь выбрал файл и щелкнул на кнопке Download (Загрузить), при­ложение открывает диалоговое окно SaveFileDialog, чтобы пользователь мог задать место записи полученного файла. Открыть окно SaveFileDialog в ответ на событие DownloadFileCompleted нельзя, потому что это событие инициировано не пользовате­лем, в результате чего будет сгенерировано исключение SecurityException._

Код открывает окно SaveFileDialog, однако приложение не пытается немедленно открыть поток FileStream, иначе файл оставался бы открытым, пока выполняется за­грузка. Чтобы избежать такой ситуации, код передает окно SaveFileDialog событию DownloadFileCompleted как статический объект с помощью необязательного второго аргумента, присутствующего во всех методах веб-служб.

private void cmdDownload_Click(object sender,

RoutedEventArgs e)

f

if (IstFiles.Selectedlndex != -1) {

SaveFileDialog saveDialog = new SaveFileDialog(); if (saveDialog.ShowDialog() == true) (

client.DownloadFileAsync(

IstFiles.Selectedltem.ToString (), saveDialog); lblStatus.Text = "Download started.";

}

}

}

Обработчик события DownloadFileCompleted извлекает объект SaveFileDialog и применяет его для создания потока FileStream. Затем обработчик копирует данные из байтового массива в файл.

private void client_DownloadFileCompleted(object sender, DownloadFileCompletedEventArgs e)

{

if (e.Error == null) {

lblStatus.Text = "Загрузка завершена успешно.";

II Получение объекта SaveFileDialog, который будет // передан при вызове службы SaveFileDialog saveDialog =

(SaveFileDialog)e.UserState;

using (Stream stream = saveDialog.OpenFile()) {

stream.Write(e.Result, 0, e.Result.Length);

}

lblStatus.Text = "Файл сохранен в " +

saveDialog.SafeFileName;

else {

lblStatus.Text = "Загрузка завершена неуспешно.";

}

}

Полезный побочный эффект такого подхода состоит в том, что код предоставляет пользователю возможность начать загружать одновременно много файлов. Для каждого файла будет открыто отдельное окно SaveFileDialog, поэтому по окончании загрузки каждый файл можно будет сохранить в нужном месте.

Код выгрузки аналогичный, но вместо SaveFileDialog он открывает окно Open" FileDialog и, кроме того, извлекает данные из файла немедленно после выбора фай­ла пользователем. Данные записываются в битовый массив и передаются методу UploadFileAsync (). Клиентский код Silverlight, решающий эти задачи, почти такой же, как код службы, используемый для открытия файла в методе DownloadFile ().

private void cmdUpload_Click(object sender,

RoutedEventArgs e)

{

OpenFileDialog, openDialog = new OpenFileDialog () ;

if (openDialog.ShowDialog() == true)

(

try {

using (Stream stream =

openDialog.File.OpenRead())

{

// Запрет выгрузки больших файлов // (более 5 МБ)

if (stream.Length < 5120000) {

byte[] data = new byte [stream. Length] ; stream.Read(data,0,(int)stream.Length);

client.UploadFileAsync(

openDialog.File.Name, data); lblStatus.Text = "Выгрузка началась.";

}

else {

lblStatus.Text =

"Объем файла не должен превышать 5 МБ." ;

}

}

}

catch {

lblStatus.Text = "Ошибка чтения файла.";

}

}

}

private void client_UploadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)

if (e.Error == null) {

lblStatus.Text = "Выгрузка успешно завершена.";

// Обновление списка файлов client.GetFileListAsync();

}

else

{

lblStatus.Text = "Загрузка завершена неуспешно.";

}

Этим завершается рассмотрение примера полнофункционального клиентского при­ложения, позволяющего обмениваться файлами с веб-сервером.

Резюме

В данной главе рассмотрены средства доступа приложения Silverlight к локальному жесткому диску. Изолированные хранилища представляют собой ограниченные в объе­ме фусцированные места сохранения данных, сериализованных объектов и параметров приложения. Класс OpenFileDialog позволяет извлекать информацию из локальных файлов, выбранных пользователем. Класс SaveFileDialog предназначен для сохране­ния информации в файле на жестком диске. Эти средства обеспечивают баланс между безопасностью и возможностями приложений Silverlight. Зловредное приложение не мо­жет с их помощью изменять локальные файлы или читать конфиденциальные данные, однако "законное" приложение может сохранять данные между сеансами и читать ин­формацию, хранящуюся в локальной файловой системе.

Источник: Мак-Дональд, Мэтью. Silverlight 3 с примерами на С# для профессионалов. : Пер. с англ. —- М. : ООО «И.Д. Вильяме», 2010. — 656 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии