13 апреля 2011 г.

Загрузка файлов на сервер через Uploadify, для ASP.Net MVC

Рано или поздно здравый смысл берет верх, и становится понятно, что для загрузки файлов на сервер нужно что-то получше, чем обычный input type="file". Сегодня мы займемся тем, что прикрутим нормальный загрузчик файлов к MVC-проекту. Кроме того, мы попробуем добавить немного дополнительного функционала для обработки загружаемого файла. Если интересно, то прошу далее...

Поможет нам Jquery-плагин Uploadify, который имеет множество нужных, дополнительных функций. В своей практике я его уже использовал несколько раз, и плохих моментов совершенно не нашел. Плагин позволяет загружать абсолютно любые форматы файлов и использовать фильтр по ним. Мы же будем загружать только картинки.

Итак, что будем делать?
Представим, что нам нужно загрузить картинку/аватар на сервер. Но сделать это нужно не стандартным способом, через POST страницы, а более логическим. Тоесть, картинка должна загружаться в положенное место без перезагрузки всей страницы. Кроме того, после успешной загрузки нам нужно увидеть, что именно мы загрузили. Итак, мы остановились на том, что пользователь заходит на страницу, выбирает картинку, и она тут-же загружается на сервер, подставляясь в обычный Image на странице для отображения. И все это, как я уже писал, должно происходить без обновления страницы.

Сама по себе, такая задача - довольно простая, поэтому я решил ее немного усложнить.
Допустим, кроме сохранения файла в нужное место, в соответственном методе нам нужно совершить еще ряд действий с полученными вместе с файлом параметрами. Это уже сложновато т.к. uploadify не делает post-а как такового, поэтому приходится что-то мудрить самому.

Вобщем, общая картина работы проекта такова:
Пользователь заходит на страницу, вводит в поле имя будущего файла, и наживает кнопку, которая переадресовывает его на страницу загрузки файлов. Там он выбирает нужный файл, загружает его на сервер, смотрит результат. Суть в том, что файл сохранится под пришедшим с предыдущей страницы названием. Суть в том, что вместе с файлом, мы будем отправлять еще один параметр (новое имя), а так как плагин инициализируется при загрузке страницы, то нам необходимо сразу при загрузке иметь этот параметр на странице. Вот он и будет приходить по редиректу с предыдущей.

Написал много всего, теперь перейдем к делу.

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

<script src="/Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>
<link href="/Content/uploadify.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/jquery.uploadify.v2.1.4.min.js" type="text/javascript"></script>
<script src="/Scripts/swfobject.js" type="text/javascript"></script>

Перейдем к странице задание имени для изображения.
Код разметки для Index.aspx:

<form method="post" id="about_frm">
        New Image Name:
        <input type="text" id="img_name" name="img_name" />
        <input type="submit" value="POST" />
    </form>
Напомню, в поле вводится новое название, и по щелчку по кнопке, делается отправка данных формы, вызывая метод контроллера:

[HttpPost]
        public ActionResult Index(FormCollection form)
        {
            string newName = 
form["img_name"] != string.Empty ? form["img_name"] : "some_name";
            return RedirectToAction("About", new { id = newName });
        }
Тут мы принимаем параметры формы (в нашем случае он всего один), и делаем редирект на другой метод, передавая в него параметр имени.
В контроллере About нам остается только принять параметр и передать его во ViewState:

public ActionResult About(string id)
        {
            ViewData["NewFileName"] = id;
            return View();
        }
Теперь к главной странице (About.aspx), на которой будет происходить выбор и загрузка картинки:

<script src="/Scripts/home.js" type="text/javascript"></script>
<table style="width: 50%; height: 300px">
<tr>
<td width="50%" align="center" valign="top">
<a id="uploadify" href="#">Link</a>
<input type="hidden" id="img_name" value="<%= ViewData["NewFileName"].ToString() %>"/>
</td>
<td width="50%" align="center">
<img id="my_image" src="" alt="" width="200px" height="200px" />
</td>
</tr>
</table>
Коротко по коду: в левой колонке таблицы находятся ссылка, на которую повешен плагин (можно использовать что угодно), и скрытое поле, куда попадает принятое контроллером значение, при загрузке страницы; в правой колонке, пустая картинка (в ней будет отображаться загруженный файл). home.js - подключенный скрипт настройки uploadify, чтобы не писать на самой странице.

Собственно, сам home.js:

$(document).ready(function () {
$("#uploadify").uploadify({
'uploader': '/Scripts/uploadify.swf',
'script': '/Home/UploadFile/' + $('#img_name').val(),
'cancelImg': '/Content/cancel.png',
'fileDesc': 'Только файлы в формате jpg или jpeg',
'auto': true,
'multi': false,
'fileExt': '*.jpg;*.jpeg;',
'width': '32',
'height': '32',
'queueSizeLimit': '1',
'buttonImg': '/Content/add.png',
'onComplete': function (event, queueID, fileObj, response, data) {
var timestamp = new Date().getTime();
$('#my_image').attr("src", response + '?' + timestamp);
$('#my_image').width(200).height(200);
}});
});

Как уже видно, код отрабатывает при загрузке всех элементов страницы. Тут к нашей ссылке привязывается плагин загрузки, с заданием ему нужных параметров.
В принципе, я думаю, что по параметрах все понятно. Нужно лишь уточнить 2 момента:
  1. 'script': '/Home/UploadFile/' + $('#img_name').val()
  2. весь блок 'onComplete'
По первому пункту:
В параметре script указывается обработчик загрузчика. Мы будем использовать один из методов контроллера. Помимо самого файла, передаем туда новое название из того самого скрытого поля на странице. Вместо контроллера здесь также можно использовать какой-нибудь HTTP Handler.

По второму пункту:
Как уже сразу понятно, в этом блоке описываются действия, происходящие после успешной загрузки файла. Но что тут делаем мы? Все просто. Метод, указанный в первом пункте, вернет нам относительный путь к новому изображению (response). Его то мы и подставляем в атрибут картинки, после чего просто задаем нужные ее размеры. А дописывание ?timestamp к источнику картинки позволяет моментально обновить изображение на странице (кстати, это решение довольно странного бага).

Заключительной частью проекта есть метод контроллера /Home/UploadFile/, к которому обращается плагин загрузки, и который содержит в себе всю логику сохранения:

   1:  public string UploadFile(HttpPostedFileBase fileData, string id)
   2:  {
   3:      if (fileData != null && fileData.ContentLength <= 1048576 && id != null)
   4:      {
   5:          string newName = id;
   6:   
   7:          string v_path = ConfigurationManager.AppSettings["ImagesPath"];
   8:          string p_path = Server.MapPath(v_path);
   9:   
  10:          int idx = fileData.FileName.LastIndexOf(".");
  11:          string ext = 
  12:    fileData.FileName.Substring(idx, fileData.FileName.Length - idx);
  13:          fileData.SaveAs(p_path + newName + ext);
  14:   
  15:          string response = v_path.Remove(0, 1) + newName + ext;
  16:          return response;
  17:      }
  18:   
  19:      return "ok";
  20:  }

Метод принимает отправленный файл, и новое имя для него.
Сначала мы проверяем, существует ли вообще файл, его имя, и если да, то меньше ли его размер 1-го мегабайта. Далее берем из конфига путь к загрузками и преобразовываем его в физический путь для сохранения файла. Вырезаем у пришедшего файла концовку - его расширение, и сохраняем файл по нужному адресу, с заданием его нового имени + старого расширения.
После этого формируется строка ответа, которая должна вернуть относительный путь к новой картинке.

Вот и все. Можно смотреть на результат.
Попадаем на первую страницу, где вводим имя будущего файла:
После нажатия на кнопку POST, мы попадаем на страницу About, где по щелчку на иконке, выбираем любое изображение. Загрузка происходит сразу после выбора файла, автоматически. После нее мы можем сразу увидеть загруженную картинку:
Сделать скриншот красивого прогресс-бара у меня не выйдет, поэтому на нее посмотрите сами )))

Окончательным результатом можно считать присутствие в нужной папке загруженного файла, с указанным на первой странице именем:
На этом все. Вышло долго, но полезно.
_____
Исходники