В этой статье Марк Салливан рассказывает, как с помощью 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 доступны для скачивания по ссылке.
Комментарии (6)
MRomaV
01.11.2017 00:23уже немного не актуальна статья на сегодняшнее время
MRomaV
01.11.2017 18:33Вот более актуальна статья www.hishambinateya.com/uploading-files-in-razor-pages
lair
HTTP-хэндлеры? В век asp.net core, OWIN и несчастного WebAPI? Спасибо, но нет.
(оригинальной статье все-таки три с половиной года, устарело уже)
ZanziPanzi
Но интересно как ретроспектива.
Evengard
Почему WebAPI несчастен?
lair
В этом конкретном списке он просто (на мой вкус) наименее подходит для загрузки файлов, особенно если остальное приложение его не использует.