В этой статье Марк Салливан рассказывает, как с помощью ASP.NET и библиотек Uploadify реализовать мультизагрузку файлов с динамическими индикаторами выполнения. Созданный в процессе класс Http-обработчика, наряду с классом элемента управления, подойдет для любого другого приложения на ASP.NET, что делает этот код, с одной стороны, полезным plug-in-play решением, а с другой – наглядной демонстрацией работы с Http-обработчиком. Он также добавил ряд дополнительных функций на уровень ASP.NET, таких как фильтрация по расширению файла, обратная передача по завершении и безопасность на основе сеансов.

С выходом HTML5 появилась надежда на возможность реализации одновременной загрузки нескольких файлов без использования ActiveX или других плагинов. Но чистый HTML5 не поддерживает динамические индикаторы во время загрузки, к тому же он по-разному обрабатывается в некоторых браузерах. Мне же было необходимо безопасное решение, позволяющее реализовать фильтрацию по расширению файла.
Я открыл для себя Uploadify, когда искал замену предыдущему решению. Эта библиотека изначально написана как бесплатный Flash-плагин, но в 2012 году было также представлено решение для HTML5. Стоимость HTML5-библиотеки варьируется от $5 до $100 в зависимости от того, собираетесь ли вы ее распространять. На мой взгляд, это невысокая цена как для такого качественного продукта, но, конечно, решать вам. В любом случае, я также сделал реализацию Flash-версии, если она вдруг кому-то пригодится. Возможно, мне и самому удастся применить ее для поддержки некоторых своих проектов в более старых браузерах. К сожалению, я пока не встречал в интернете каких-либо наглядных примеров реализации этого решения на ASP.NET.

Я заключил опции в оболочку веб-элемента управления C# и класса C# (для записи напрямую в выходной поток без элементов управления). Классы обрабатывают большинство опций, обрабатываемых библиотекой. Я также добавил поддержку JavaScript для фильтрации загружаемых файлов со стороны сервера и клиента. Что касается безопасности и фильтрации на стороне сервера, для их реализации требуется состояние сессии. Тем не менее они помогают предотвратить нежелательные загрузки. Другие необходимые для проекта опции тоже добавлены в классы или элементы управления C#.
Вступительные замечания
Прежде чем использовать предоставленный код, вам следует выбрать между бесплатной Flash-библиотекой и платной HTML5-библиотекой. Всё содержимое первой нужно поместить в пустую подпапку Uploadify, а второй – в UploadiFive. Flash-версия используется в третьем демо (Demo3.aspx), а HTML5-версия – в первых двух.
Или же вы можете обернуть классы вокруг поля мультизагрузки файлов в HTML5.
Подключение библиотек
Необходимо добавить ссылки к нужной библиотеке jQuery и выбранной библиотеке Uploadify. Стандартный файл CSS поставляется с пакетами, поэтому я добавил в своем коде ссылку и на него. Добавьте следующие строки внутри тега head вашей формы ASPX.
или так:
Добавление на страницу элемента управления
В веб-формах ASP.NET добавить элемент управления на страницу можно, создав экземпляр кода класса UploadiFiveControl и добавив его в форму или заполнитель.
Добавление элемента управления в MVC происходит аналогичным образом.
Работать с Flash-версией довольно просто, так как она использует те же классы и элемент управления, но флаг версии выставлен на Flash, а не на default. Остальные изменения, включая названия некоторый опций, обрабатываются элементом управления.
Http-обработчик для загруженных файлов
Также необходимо добавить в проект класс Http-обработчика UploadiFiveFileHandler, который обрабатывает файлы по мере их загрузки на сервер. Эта часть реализации меньше всего описана где-либо в справочниках или блогах, и именно поэтому я посвятил ей весь следующий раздел. Чтобы включить этот класс, скопируйте файлы UploadiFiveFileHandler.ashx и UploadiFiveFileHandler.ashx.cs в ваш проект.
Http-обработчик и безопасность
Создать Http-обработчик довольно просто, но гораздо сложнее обеспечить его гибкость и внести некоторые дополнительные функции. Я использовал состояние сессии, чтобы обеспечить безопасность и передачу информации с загружающей страницы в обработчик. Для этого я создал класс UploadiFive_Security_Token. Вот как выглядит его объявление:
В данном классе содержится путь к файлу, допустимые расширения файла и ключ, по которому обработчик будет находить файл. При рендеринге класса UploadiFiveControl создается новый токен безопасности, который хранится в состоянии сессии под GUID. Сам GUID добавляется в словарь FormData, потом передается в библиотеку Uploadify и затем возвращается в обработчик с данными файла. После этого обработчик может отыскать в сессии токен безопасности, основанный на соответствующем GUID.
Данные формы включены в код JavaScript для создания элемента управления Uploadify. Ниже приведен пример полученного в результате кода HTML без каких-либо ограничений по расширению файла:
Как видно выше, генерируемый токеном безопасности GUID включается в код JavaScript для инициализации библиотеки.
Http-обработчику требуется доступ к состоянию сессии, поэтому ему необходимо реализовать интерфейс IReadOnlySessionState. Вот как выглядит полный код метода обработчика ProcessRequest без каких-либо ограничений по расширению файла:
Если токен в сессии не найден, файл не будет допущен к загрузке.
Ограничения по расширению файла
Хорошая практика при разработке загрузчика – добавлять механизм ограничения файлов по их расширению. В этом разделе я покажу, как мне удалось это сделать на клиентской и серверной стороне с учетом рассмотренных выше параметров безопасности.
Проверка на клиентской стороне реализована как событие в библиотеке UploadiFive. Но событие добавления файла onChange может быть использовано и за пределами библиотеки.
Допустимые расширения передаются в элемент управления UploadiFiveControl в виде строки, разделенной запятыми.
Если всё задано верно, код JavaScript прикрепляется к событию onAddQueueItem при рендеринге HTML.
Этот код берет список расширений, преобразованный элементом управления C# в JSON- массив и сверяет конкретное расширение с данными массива. Если это расширение недопустимо, пользователь получает предупреждение, а файл удаляется из очереди на загрузку.
Код Http-обработчика на серверной стороне выглядит примерно так же. В нем токен безопасности используется для получения списка допустимых расширений:
Теперь ограничение недопустимых форматов на клиентской и серверной стороне можно считать реализованным.
Ограничения по размеру файла
Существует также ряд способов ограничить загрузку файлов по их размеру. У сервера IIS имеются такие ограничения, и об этом следует помнить при работе. К тому же в Uploadify тоже есть способ добиться ограничения размеров загружаемых файлов, о чем я рассказывал.
В файле web.config можно задать максимальный размер для допустимого типа контента, что будет накладывать ограничения на загружаемые файлы. За это отвечает тег requestLimits, принимающий значения в количестве байтов. К примеру, фрагмент кода ниже ограничивает размер загружаемого файла примерно до 200 Мб. Этот фрагмент тоже был включен в код в файле web.config.
Помимо этого, библиотека Uploadify использует свойство JavaScript fileSizeLimit в качестве FileSizeLimit в классах C#. Это выглядит как строка вроде “100KB” или “200MB”.
Flash как резервный вариант и вопросы совместимости
Некоторые популярные браузеры, в особенности IE9, не получают необходимых обновлений для того, чтобы поддерживать HTML5. Потому, если в нашем случае не позаботиться о резервном варианте, пользователь старого браузера увидит только стандартное для HTML поле загрузки файла без кнопки загрузки. При этом, конечно же, ничего не будет работать.
К счастью, мы можем взять в качестве резервного варианта Flash-версию, которая очень похожа на HTML5-версию. Для этого используется событие onFallback. Оно было дополнительно реализовано в классах C#, доступных для загрузки здесь. Если добавить эту функцию в текущий код с помощью элемента управления UploadiFiveControl, получится примерно такой код:
В коде ниже мы указываем классу добавить резервный вариант Flash, выставив свойство RevertToFlashVersion на true. Если же браузер не поддерживает ни HTML5, ни Flash, можно вывести окно с предупреждением, используя свойство NoHtml5OrFlashMessage.
Пример выше генерирует следующий код JavaScript, который будет включен в код HTML вашей aspx-страницы:
Теперь у нас есть рабочий резервный вариант для старых браузеров, который практически идентичен основной версии. Для срабатывания резервного механизма необходима Flash-версия, но только в качества второстепенного варианта, как в моем примере. Конечно же, вам нужно будет скачать обе версии и привязать ссылки на соответствующие им CSS- и JS-файлы внутри тега head.
Ссылка на код:
Весь код и четыре демо-страницы aspx доступны для скачивания по ссылке.

Общая информация
С выходом HTML5 появилась надежда на возможность реализации одновременной загрузки нескольких файлов без использования ActiveX или других плагинов. Но чистый HTML5 не поддерживает динамические индикаторы во время загрузки, к тому же он по-разному обрабатывается в некоторых браузерах. Мне же было необходимо безопасное решение, позволяющее реализовать фильтрацию по расширению файла.
Я открыл для себя Uploadify, когда искал замену предыдущему решению. Эта библиотека изначально написана как бесплатный Flash-плагин, но в 2012 году было также представлено решение для HTML5. Стоимость HTML5-библиотеки варьируется от $5 до $100 в зависимости от того, собираетесь ли вы ее распространять. На мой взгляд, это невысокая цена как для такого качественного продукта, но, конечно, решать вам. В любом случае, я также сделал реализацию Flash-версии, если она вдруг кому-то пригодится. Возможно, мне и самому удастся применить ее для поддержки некоторых своих проектов в более старых браузерах. К сожалению, я пока не встречал в интернете каких-либо наглядных примеров реализации этого решения на ASP.NET.

Я заключил опции в оболочку веб-элемента управления C# и класса C# (для записи напрямую в выходной поток без элементов управления). Классы обрабатывают большинство опций, обрабатываемых библиотекой. Я также добавил поддержку JavaScript для фильтрации загружаемых файлов со стороны сервера и клиента. Что касается безопасности и фильтрации на стороне сервера, для их реализации требуется состояние сессии. Тем не менее они помогают предотвратить нежелательные загрузки. Другие необходимые для проекта опции тоже добавлены в классы или элементы управления C#.
Использование кода
Вступительные замечания
Прежде чем использовать предоставленный код, вам следует выбрать между бесплатной Flash-библиотекой и платной HTML5-библиотекой. Всё содержимое первой нужно поместить в пустую подпапку Uploadify, а второй – в UploadiFive. Flash-версия используется в третьем демо (Demo3.aspx), а HTML5-версия – в первых двух.
Или же вы можете обернуть классы вокруг поля мультизагрузки файлов в HTML5.
Подключение библиотек
Необходимо добавить ссылки к нужной библиотеке jQuery и выбранной библиотеке Uploadify. Стандартный файл CSS поставляется с пакетами, поэтому я добавил в своем коде ссылку и на него. Добавьте следующие строки внутри тега head вашей формы ASPX.
<script src="jquery/jquery-1.10.2.js" type="text/javascript"></script> <script src="uploadifive/jquery.uploadifive.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="uploadifive/uploadifive.css" />
или так:
<script src="jquery/jquery-1.10.2.js" type="text/javascript"></script> <script src="uploadify/jquery.uploadify.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="uploadify/uploadify.css" />
Добавление на страницу элемента управления
В веб-формах ASP.NET добавить элемент управления на страницу можно, создав экземпляр кода класса UploadiFiveControl и добавив его в форму или заполнитель.
// Get the save path string savepath = Context.Server.MapPath("files"); // Create the upload control and add to the form UploadiFiveControl uploadControl = new UploadiFiveControl { UploadPath = savepath, RemoveCompleted = true, SubmitWhenQueueCompletes = true, AllowedFileExtensions = ".jpg|.bmp" }; form1.Controls.Add(uploadControl);
Добавление элемента управления в MVC происходит аналогичным образом.
Работать с Flash-версией довольно просто, так как она использует те же классы и элемент управления, но флаг версии выставлен на Flash, а не на default. Остальные изменения, включая названия некоторый опций, обрабатываются элементом управления.
// Create the upload control and add to the form UploadiFiveControl uploadControl = new UploadiFiveControl { UploadPath = savepath, RemoveCompleted = true, SubmitWhenQueueCompletes = true, AllowedFileExtensions = ".jpg|.bmp", Version = UploadiFive_Version_Enum.Flash }; form1.Controls.Add(uploadControl);
Http-обработчик для загруженных файлов
Также необходимо добавить в проект класс Http-обработчика UploadiFiveFileHandler, который обрабатывает файлы по мере их загрузки на сервер. Эта часть реализации меньше всего описана где-либо в справочниках или блогах, и именно поэтому я посвятил ей весь следующий раздел. Чтобы включить этот класс, скопируйте файлы UploadiFiveFileHandler.ashx и UploadiFiveFileHandler.ashx.cs в ваш проект.
Http-обработчик и безопасность
Создать Http-обработчик довольно просто, но гораздо сложнее обеспечить его гибкость и внести некоторые дополнительные функции. Я использовал состояние сессии, чтобы обеспечить безопасность и передачу информации с загружающей страницы в обработчик. Для этого я создал класс UploadiFive_Security_Token. Вот как выглядит его объявление:
/// <summary> Token used to add security, via adding a key to the session /// state, for uploading documents through this system </summary> public class UploadiFive_Security_Token { /// <summary> Path where the uploaded files should go </summary> public readonly string UploadPath; /// <summary> List of file extensions allowed </summary> public readonly string AllowedFileExtensions; /// <summary> Name of the file object to use in your server-side script</summary> public readonly string FileObjName; /// <summary> The GUID for this security token </summary> public readonly Guid ThisGuid; /// <summary> Constructor for a new instance of the UploadiFive_Security_Token class </summary> /// <param name="UploadPath" /> Path where the uploaded files should go /// <param name="AllowedFileExtensions" /> List of file extensions allowed /// <param name="FileObjName" /> Name of file object to use in your server-side script public UploadiFive_Security_Token(string UploadPath, string AllowedFileExtensions, string FileObjName ) { this.UploadPath = UploadPath; this.AllowedFileExtensions = AllowedFileExtensions; this.FileObjName = FileObjName; ThisGuid = Guid.NewGuid(); } }
В данном классе содержится путь к файлу, допустимые расширения файла и ключ, по которому обработчик будет находить файл. При рендеринге класса UploadiFiveControl создается новый токен безопасности, который хранится в состоянии сессии под GUID. Сам GUID добавляется в словарь FormData, потом передается в библиотеку Uploadify и затем возвращается в обработчик с данными файла. После этого обработчик может отыскать в сессии токен безопасности, основанный на соответствующем GUID.
// Create a new security token with all the configuration info UploadiFive_Security_Token newToken = new UploadiFive_Security_Token(UploadPath, AllowedFileExtensions, FileObjName); // Add this token to the current session for the HttpHandler HttpContext.Current.Session["#UPLOADIFIVE::" + newToken.ThisGuid.ToString()] = newToken; // Save the token into the formdata so comes to the HttpHandler FormData["token"] = newToken.ThisGuid.ToString();
Данные формы включены в код JavaScript для создания элемента управления Uploadify. Ниже приведен пример полученного в результате кода HTML без каких-либо ограничений по расширению файла:
<input id="file_upload" name="file_upload" class="file_upload" type="file" /> <script type="text/javascript"> $(document).ready(function() { $('#file_upload').uploadifive({ 'fileObjName': 'Filedata', 'formData': { 'token' : 'da66e0ad-750b-4d76-a016-72633dea8b53' }, 'onQueueComplete': function (uploads) { $('#file_upload').closest("form").submit(); }, 'uploadScript': 'UploadiFiveFileHandler.ashx' }); }); </script>
Как видно выше, генерируемый токеном безопасности GUID включается в код JavaScript для инициализации библиотеки.
Http-обработчику требуется доступ к состоянию сессии, поэтому ему необходимо реализовать интерфейс IReadOnlySessionState. Вот как выглядит полный код метода обработчика ProcessRequest без каких-либо ограничений по расширению файла:
Context.Response.ContentType = "text/plain"; Context.Response.Expires = -1; // Try to get the security token key string tokenKey = Context.Request["token"]; if (tokenKey == null) { Context.Response.Write("No token provided with this request"); Context.Response.StatusCode = 401; return; } // Try to get the matching token object from the session UploadiFive_Security_Token tokenObj = Context.Session["#UPLOADIFIVE::" + tokenKey] as UploadiFive_Security_Token; if (tokenObj == null) { Context.Response.Write("No matching server-side token found for this request"); Context.Response.StatusCode = 401; return; } try { // Get the posted file from the appropriate file key HttpPostedFile postedFile = Context.Request.Files[ tokenObj.FileObjName ]; if (postedFile != null) { // Get the path from the token and ensure it exists string path = tokenObj.UploadPath; if (!Directory.Exists(path)) Directory.CreateDirectory(path); string filename = Path.GetFileName(postedFile.FileName); postedFile.SaveAs(path + @"\" + filename); // Post a successful status Context.Response.Write(filename); Context.Response.StatusCode = 200; } } catch (Exception ex) { Context.Response.Write("Error: " + ex.Message); Context.Response.StatusCode = 500; }
Если токен в сессии не найден, файл не будет допущен к загрузке.
Ограничения по расширению файла
Хорошая практика при разработке загрузчика – добавлять механизм ограничения файлов по их расширению. В этом разделе я покажу, как мне удалось это сделать на клиентской и серверной стороне с учетом рассмотренных выше параметров безопасности.
Проверка на клиентской стороне реализована как событие в библиотеке UploadiFive. Но событие добавления файла onChange может быть использовано и за пределами библиотеки.
Допустимые расширения передаются в элемент управления UploadiFiveControl в виде строки, разделенной запятыми.
uploadControl.AllowedFileExtensions = ".jpg|.bmp";
Если всё задано верно, код JavaScript прикрепляется к событию onAddQueueItem при рендеринге HTML.
<script type="text/javascript"> $(document).ready(function() { $('#file_upload').uploadifive({ 'fileObjName': 'Filedata', 'formData': { 'token' : '4c893799-fd21-4d85-80c4-e32e6cacc794' }, 'removeCompleted': true, 'onAddQueueItem' : function(file) { var extArray = JSON.parse('[ ".jpg", ".bmp" ]'); var fileName = file.name; var ext = fileName.substring(fileName.lastIndexOf('.')).toLowerCase(); var isExtValid = false; for(var i = 0; i < extArray.length; i++) { if ( ext == extArray[i] ) { isExtValid = true; break; } } if ( !isExtValid ) { alert("File types of '<extension>' are not allowed".replace('<extension>', ext)); $('#file_upload').uploadifive('cancel', file); } }, 'uploadScript': 'UploadiFiveFileHandler.ashx' }); }); </script>
Этот код берет список расширений, преобразованный элементом управления C# в JSON- массив и сверяет конкретное расширение с данными массива. Если это расширение недопустимо, пользователь получает предупреждение, а файл удаляется из очереди на загрузку.
Код Http-обработчика на серверной стороне выглядит примерно так же. В нем токен безопасности используется для получения списка допустимых расширений:
// Get the filename for the uploaded file string filename = Path.GetFileName(postedFile.FileName); // Are there file extension restrictions? if ( !String.IsNullOrEmpty(tokenObj.AllowedFileExtensions)) { string extension = Path.GetExtension(postedFile.FileName).ToLower(); List<string> allowed = tokenObj.AllowedFileExtensions.Split("|,".ToCharArray()).ToList(); if (!allowed.Contains(extension)) { Context.Response.Write("Invalid extension"); Context.Response.StatusCode = 401; return; } } // Save this file locally postedFile.SaveAs(path + @"\" + filename);
Теперь ограничение недопустимых форматов на клиентской и серверной стороне можно считать реализованным.
Ограничения по размеру файла
Существует также ряд способов ограничить загрузку файлов по их размеру. У сервера IIS имеются такие ограничения, и об этом следует помнить при работе. К тому же в Uploadify тоже есть способ добиться ограничения размеров загружаемых файлов, о чем я рассказывал.
В файле web.config можно задать максимальный размер для допустимого типа контента, что будет накладывать ограничения на загружаемые файлы. За это отвечает тег requestLimits, принимающий значения в количестве байтов. К примеру, фрагмент кода ниже ограничивает размер загружаемого файла примерно до 200 Мб. Этот фрагмент тоже был включен в код в файле web.config.
<configuration> <system.webServer> <security> <requestLimits maxAllowedContentLength="209715200" /> </security> </system.webServer> </configuration>
Помимо этого, библиотека Uploadify использует свойство JavaScript fileSizeLimit в качестве FileSizeLimit в классах C#. Это выглядит как строка вроде “100KB” или “200MB”.
Flash как резервный вариант и вопросы совместимости
Некоторые популярные браузеры, в особенности IE9, не получают необходимых обновлений для того, чтобы поддерживать HTML5. Потому, если в нашем случае не позаботиться о резервном варианте, пользователь старого браузера увидит только стандартное для HTML поле загрузки файла без кнопки загрузки. При этом, конечно же, ничего не будет работать.
К счастью, мы можем взять в качестве резервного варианта Flash-версию, которая очень похожа на HTML5-версию. Для этого используется событие onFallback. Оно было дополнительно реализовано в классах C#, доступных для загрузки здесь. Если добавить эту функцию в текущий код с помощью элемента управления UploadiFiveControl, получится примерно такой код:
// Create the upload control and add to the form UploadiFiveControl uploadControl = new UploadiFiveControl { UploadPath = savepath, RemoveCompleted = true, Version = UploadiFive_Version_Enum.HTML5, RevertToFlashVersion = true, NoHtml5OrFlashMessage = "Please try a more current browser" };
В коде ниже мы указываем классу добавить резервный вариант Flash, выставив свойство RevertToFlashVersion на true. Если же браузер не поддерживает ни HTML5, ни Flash, можно вывести окно с предупреждением, используя свойство NoHtml5OrFlashMessage.
Пример выше генерирует следующий код JavaScript, который будет включен в код HTML вашей aspx-страницы:
<script type="text/javascript"> $(document).ready(function() { $('#file_upload').uploadifive({ 'fileObjName': 'Filedata', 'formData': { 'token' : 'f8916a9f-9dda-441f-a58b-13948e61f7e7' }, 'removeCompleted': true, 'uploadScript': 'UploadiFiveFileHandler.ashx', 'onFallback': function() { // Revert to flash version if no HTML5 $('#file_upload').uploadify({ 'fileObjName': 'Filedata', 'formData': { 'token' : 'f8916a9f-9dda-441f-a58b-13948e61f7e7' }, 'removeCompleted': true, 'swf': 'uploadify/uploadify.swf', 'uploader': 'UploadiFiveFileHandler.ashx', 'onFallback': function() { alert('Please try a more current browser'); } }); } }); }); </script>
Теперь у нас есть рабочий резервный вариант для старых браузеров, который практически идентичен основной версии. Для срабатывания резервного механизма необходима Flash-версия, но только в качества второстепенного варианта, как в моем примере. Конечно же, вам нужно будет скачать обе версии и привязать ссылки на соответствующие им CSS- и JS-файлы внутри тега head.
Ссылка на код:
Весь код и четыре демо-страницы aspx доступны для скачивания по ссылке.

