25 сентября 2012 г.

Загрузка файлов на сервер, используя Kendo UI Upload в ASP.Net MVC. Часть 2

Сегодня, во второй части (первая часть) знакомства с Kendo UI, мы будем практиковаться в асинхронной загрузке файлов на сервер.

Рассмотрим 4 варианта асинхронной загрузки:
  1. Простая загрузка файла;
  2. Загрузка нескольких файлов, onSuccess, onError;
  3. Загрузка файла с метаданными;
  4. Загрузка фала используя Drag&Drop.

Простая загрузка файла

Самый обычный вариант загрузки - выбранный файл автоматически начинает загружаться на сервер.

Вот наше представление:

   1:  <div style="width: 45%">
   2:      <div>
   3:          <input name="inputFile" id="inputFile" type="file" />
   4:      </div>
   5:  </div>
   6:  <script type="text/javascript">
   7:      $(function () {
   8:          $("#inputFile").kendoUpload({
   9:              multiple: false,
  10:              async: {
  11:                  saveUrl: '@Url.Action("asyncuploadaction", "home")'
  12:              }
  13:          });
  14:      });
  15:  </script>

В настройках плагина мы лишь указали, что загрузка должна происходить асинхронно, а также написали Action, на который будет отослан выбранный файл.
А вот и сам Action:

   1:  [HttpPost]
   2:  public ActionResult AsyncUploadAction(HttpPostedFileBase inputFile)
   3:  {
   4:      if (inputFile != null)
   5:      {
   6:          string virtualPath = ConfigurationManager.AppSettings["Storage"] +                                           inputFile.FileName;
   7:          string physicalPath = Server.MapPath(virtualPath);
   8:          if (System.IO.File.Exists(physicalPath))
   9:              System.IO.File.Delete(physicalPath);
  10:          inputFile.SaveAs(physicalPath);
  11:   
  12:          return Json(new { status = bool.TrueString }, 
                            JsonRequestBehavior.AllowGet);
  13:      }
  14:   
  15:      return Content(bool.FalseString);
  16:  }

Самым важным моментом здесь является то, что плагин загрузки должен получить с сервера ответ в виде JSON-данных. В ответ можно включать абсолютно любые данные. Если в ответ приходит на JSON, а что-либо  другое, то это означает ошибку.

Сейчас же у нас заработала обычная асинхронная загрузка сразу после выбора файла:

Загрузка нескольких файлов, и их асинхронное удаление с сервера

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

   1:  <div style="width: 45%">
   2:      <div>
   3:          <input name="inputFile" id="inputFile" type="file" />
   4:      </div>
   5:      <br />
   6:      <span id="consoleSpan" style="font-style:italic; font-size:smaller;">
             </span>
   7:  </div>

Здесь, consoleSpan - контейнер, в который будет выводиться информация об операциях загрузчика.
Самое главное происходит в скрипте:

   1:  <script type="text/javascript">
   2:      $(function () {
   3:          $("#inputFile").kendoUpload({
   4:              async: {
   5:                  saveUrl: '@Url.Action("asyncuploadaction", "home")',
   6:                  removeUrl: '@Url.Action("asyncremoveaction", "home")'
   7:              },
   8:              success: onSuccess,
   9:              error: onError
  10:          });
  11:      });
  12:   
  13:      function onSuccess(e) {        
  14:          if (e.operation == 'remove' && e.response.status == 'True') {
  15:              $('#consoleSpan').append('File ' + e.files[0].name + 
                        ' successfully removed').append('</br>');
  16:          };
  17:   
  18:          if (e.operation == 'upload' && e.response.status == 'True') {
  19:              $('#consoleSpan').append('File ' + e.files[0].name + 
                        ' successfully uploaded').append('</br>');
  20:          };
  21:      };
  22:   
  23:      function onError(e) {
  24:          if (e.operation == 'remove') {
  25:              $('#consoleSpan').append('Unable to remove file '
                           e.files[0].name).append('</br>');
  26:          };
  27:   
  28:          if (e.operation == 'upload') {
  29:              $('#consoleSpan').append('Unable to upload file '
                           e.files[0].name).append('</br>');
  30:          };
  31:      };
  32:  </script>

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

Взглянем на функции onSuccess и onError.
Обе они принимают параметр загрузчика, в котором содержится базовая информация о файле. Также там есть свойство operation, который позволяет определить тип операции над файлом. А в свойстве response - содержится весь ответ сервера (в нашем случае возвращается только параметр status). В обоих функциях мы просто выводим сообщение в "консоль".

А вот и Action, который будет обрабатывать удаление файла, и возвращать результат загрузчику. Он принимает лишь имена выбранных для удаления файлов:

   1:  [HttpPost]
   2:  public ActionResult AsyncRemoveAction(string[] fileNames)
   3:  {
   4:      foreach (var fName in fileNames)
   5:      {
   6:          string virtualPath = ConfigurationManager.AppSettings["Storage"] +                                            fName;
   7:          string physicalPath = Server.MapPath(virtualPath);
   8:          if (System.IO.File.Exists(physicalPath))
   9:              System.IO.File.Delete(physicalPath);
  10:   
  11:          return Json(new { status = bool.TrueString }, 
                                  JsonRequestBehavior.AllowGet);
  12:      }
  13:   
  14:      return Content(bool.FalseString);
  15:  }

В результате, после выбора нескольких файлов, видим картину:

Теперь удаляем ненужный файл:

А в случае ошибки загрузки\удаления, можно увидеть такое:

Загрузка файла с метаданными

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

Вот представление:

   1:  <div style="width: 45%">
   2:      <div id="filenameDiv" style="display: none;">
   3:          New file name:<input id="f_name" type="text" 
                                 class="k-textbox" value="" />
   4:      </div>
   5:      <div>
   6:          <input name="inputFile" id="inputFile" type="file" />
   7:      </div>
   8:      <br />
   9:      <span id="consoleSpan" style="font-style:italic; font-size:smaller;">
                          </span>
  10:  </div>

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

Посмотрим на скрипт:

   1:  $(function () {
   2:      $("#inputFile").kendoUpload({
   3:          multiple: false,
   4:          async: {
   5:              saveUrl: '@Url.Action("asyncuploadnameaction", "home")',
   6:              autoUpload: false,
   7:          },
   8:          success: function (e) {
   9:              if (e.operation == 'upload' && e.response.status == 'True') {
  10:                  var newName = $('#f_name').val().trim().length > 0 ? 
  11:                              ($('#f_name').val() + e.files[0].extension) :                                           e.files[0].name;
  12:                  $('#consoleSpan').append(
  13:                      'File "' + e.files[0].name
  14:                      + '" successfully uploaded with new name "'
  15:                      + newName + '"').append('</br>');
  16:                  $('#filenameDiv').hide();
  17:                  $('#f_name').val('');
  18:              };
  19:          },
  20:          select: function (e) {
  21:              $('#filenameDiv').show();
  22:          },
  23:          upload: function (e) {
  24:              e.data = {
  25:                  f_name: $('#f_name').val()
  26:              };
  27:          }
  28:      });
  29:  });

Мы задаем основные настройки загрузчика. Среди них тип работы (асинхронно), адрес для загрузки, а так же отключаем автозагрузку файлов.
Далее реализовано три события: onSuccess, onSelect и onUpload.

OnSelect - отрабатывает после выбора файла для загрузки. Мы показываем скрытый ранее див с полем для ввода нового имени.
onUpload - наступает перед стартом загрузки. Мы передаем в данные запроса дополнительный параметр f_name, со значением введенного имени.
onSuccess - проверяем статус ответа сервера и выводим сообщение в нашу консоль. Сообщение формируется в зависимости от того, ввел ли пользователь новое имя для файла.

Осталось лишь написать обработчик запроса, тоесть Action, который будет принимать и сохранять файл под нужным именем:

   1:  [HttpPost]
   2:  public ActionResult AsyncUploadNameAction(HttpPostedFileBase inputFile, 
                                                    string f_name)
   3:  {
   4:      if (inputFile != null)
   5:      {
   6:          string newName = string.Empty;
   7:          if (!string.IsNullOrEmpty(f_name))
   8:          {
   9:              int idx = inputFile.FileName.LastIndexOf(".");
  10:              string ext = inputFile.FileName.Substring(idx, 
                                        inputFile.FileName.Length - idx);
  11:              newName = f_name + ext;
  12:          }
  13:          else
  14:              newName = inputFile.FileName;
  15:   
  16:          string virtualPath = ConfigurationManager.AppSettings["Storage"] +                                              newName;
  17:          string physicalPath = Server.MapPath(virtualPath);
  18:          if (System.IO.File.Exists(physicalPath))
  19:              System.IO.File.Delete(physicalPath);
  20:          inputFile.SaveAs(physicalPath);
  21:   
  22:          return Json(new { status = bool.TrueString }, 
                                           JsonRequestBehavior.AllowGet);
  23:      }
  24:   
  25:      return Content(bool.FalseString);
  26:  }

Здесь мы проверяем, было ли принято новое имя файла. Если да, то сохраняем под ним, предварительно выделив расширение. Если нет, то сохраняем файл как есть.

Таким образом, теперь, после выбора файла, появляется поле для имени и кнопка загрузки:

И после начала загрузки увидим следующее:

Загрузка фала используя Drag&Drop

На последок хотелось бы показать еще одну интересную возможность Kendo UI Uploader. А именно - выбора файла перетаскиванием его в браузер. Стоит заметить, что данная возможность возможна только при асинхронной загрузке.

Базовый вариант такой загрузки реализуется довольно просто. На странице нам нужен только сам загрузчик, консольный элемент для наглядности, и небольшой скрипт настройки:

   1:  <div style="width: 45%">
   2:      <div>
   3:          <input name="inputFile" id="inputFile" type="file" />
   4:      </div>
   5:      
   6:      <br />
   7:      <span id="consoleSpan" style="font-style:italic; font-size:smaller;">
                                    </span>
   8:  </div>
   9:  <script type="text/javascript">
  10:      $(function () {
  11:          $("#inputFile").kendoUpload({
  12:              async: {
  13:                  saveUrl: '@Url.Action("asyncuploadaction", "home")'
  14:              },
  15:              success: onSuccess
  16:          });
  17:      });
  18:   
  19:      function onSuccess(e) {
  20:          if (e.operation == 'upload' && e.response.status == 'True') {
  21:              $('#consoleSpan').append('File ' + e.files[0].name + 
                                ' successfully uploaded').append('</br>');
  22:          };
  23:      };
  24:  </script>

Тут все стандартно - инициализируем асинхронный загрузчик по конкретному адресу, и реализуем обработку его события onSuccess. Как видно из кода, в настройках нет никаких упоминаний о Drag&Drop. Как же его "включить"? Для этого достаточно объявить на странице два специальных css-правила:

   1:  <style type="text/css">
   2:      div.k-dropzone {
   3:          border: 1px solid #c5c5c5;
   4:      }
   5:   
   6:      div.k-dropzone em {
   7:          visibility: visible;
   8:      }
   9:  </style>

Этого будет достаточно.
Вот как будет выглядеть представление в браузере:
Да-да, туда действительно можно "бросать" файлы.

Ну и результат:


Вот и все.
В окончание статьи хочу еще прояснить одну ситуацию с плагином. Речь идет о асинхронной загрузке нескольких файлов. Дело в том, что разработчики Kendo UI не могут объяснить, в каких ситуациях несколько файлов передаются на сервер массивом в одном запросе, а в каких случаях запрос отрабатывает каждый раз для следующего в очереди файла. Говорят, зависит от браузера. Говорят, но не утверждают.

_____
Исходники