Загрузка файла и выгрузка при помощи HttpHandler или Download + Upload

ru-RU | создано: 08.10.2013 | опубликовано: 08.10.2013 | обновлено: 01.01.2018 | просмотров за всё время: 14494

Недавно пришлось реализовывать возможность выгрузки (upload) на сервер файлов большого размера и выдачи (download) этого файла пользователю. Делать это решил при помощи HttpHandler. Сам принцип при использовании IHttpHandler очень просто, но есть некоторые нюансы.

Выбор типа сообщения

Когда вы выдаете пользователю файл для загрузки, ему показывается диалоговое окно. Окно имеет кнопки “Открыть”, “Сохранить” и “Отмена”. Если вы хотите чтобы вместо этих кнопок был другой диалог “Сохранить”, следует использовать:

Response.AddHeader("Content-Disposition", "inline; filename=" + file.Name);

вместо:

Response.AddHeader("Content-Disposition", "attachment; filename=" + file.Name);

А какой ContentType?

В процессе формирования ответа от сервера, не важно в каком контексте Web API, HttpHandler, HttpModule или даже простой ActionResult, или даже WebForms, обязательно надо указывать тип возвращаемого содержания. Предлагаю вам в помощь вспомогательную функцию.

// Получение типа по расширению файла
// выдаваемого при загрузке
private string GetFileExtension(string fileExtension)
{
   switch (fileExtension)
   {
      case ".htm":
      case ".html":
      case ".log":
         return "text/HTML";
      case ".txt":
         return "text/plain";
      case ".doc":
         return "application/ms-word";
      case ".tiff":
      case ".tif":
         return "image/tiff";
      case ".asf":
         return "video/x-ms-asf";
      case ".avi":
         return "video/avi";
      case ".zip":
         return "application/zip";
      case ".xls":
      case ".csv":
         return "application/vnd.ms-excel";
      case ".gif":
         return "image/gif";
      case ".jpg":
      case "jpeg":
         return "image/jpeg";
      case ".bmp":
         return "image/bmp";
      case ".wav":
         return "audio/wav";
      case ".mp3":
         return "audio/mpeg3";
      case ".mpg":
      case "mpeg":
         return "video/mpeg";
      case ".rtf":
         return "application/rtf";
      case ".asp":
         return "text/asp";
      case ".pdf":
         return "application/pdf";
      case ".fdf":
         return "application/vnd.fdf";
      case ".ppt":
         return "application/mspowerpoint";
      case ".dwg":
         return "image/vnd.dwg";
      case ".msg":
         return "application/msoutlook";
      case ".xml":
      case ".sdxl":
         return "application/xml";
      case ".xdp":
         return "application/vnd.adobe.xdp+xml";
      default:
         return "application/octet-stream";
   }
}

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

Пример использования

На простом примере покажу, как можно использовать указанный метод. Допустим у меня проект WebForms. На одной какой-то странице выдается файл (word.doc) для загрузки. Пусть в методе Page_Load вызывается моя приватная процедура DownloadFile():

private void DownloadFile()
{
   var filepath = Server.MapPath("word.doc");
   FileInfo file = new FileInfo(filepath);
   if (file.Exists)
   {
      Response.ClearContent();
      Response.AddHeader("Content-Disposition", "attachment; filename=" + file.Name);
      Response.AddHeader("Content-Length", file.Length.ToString());
      var ct = GetFileExtension(file.Extension.ToLower());
      Response.ContentType = ct;
      Response.TransmitFile(file.FullName);
      Resoinse.Close();
      Response.End();
   }
}

В строке 10 как раз и вызывается приведенный в помощь метод, который по расширению устанавливает “правильный” тип возвращаемого контента.

Response.TransmitFile() vs Response.WriteFile()

Пару слов про строку 12 предыдущего листинга. Существует два способа “отдать” пользователю файл. Вам выбирать какой использовать.

TransmitFile() – метод отправляет файл на клиент без загрузки этого файла в память сервера (application memory). Это более правильное решение для загрузки файлов большого размера.

WriteFile() - метод загружает файл в память сервера перед тем как отправить его клиенту. Этот метод более предпочтителен при загружзе маленьких и средних по размеру файлов.

Upload

С выгрузкой файлов большого размера нет ничего сложного. Чтобы не быть голословным приведу пример загрузки файлов, а точнее картинок на сервер. Я использовал этот HttpHandler для выгрузки на сервер картинок из Silverlight-приложениия.

public class ReseiverImage : IHttpHandler {

    public void ProcessRequest(HttpContext context) {
        var fileName = context.Request.QueryString["filename"];
        var prodID = int.Parse(context.Request.QueryString["prodid"]);
        var savePath = context.Server.MapPath("~/ImagesProd/" + prodID + "/");
        if (!Directory.Exists(savePath)) {
            Directory.CreateDirectory(savePath);
        }

        var tmp = Image.FromStream(context.Request.InputStream);
        if ((tmp.Width > 500) || (tmp.Height > 500)) return;
        SaveFile(tmp, savePath + fileName);
        context.Response.Write("true");
    }

    private void SaveFile(Image img, string fileSavePath) {
        img.Save(fileSavePath);
    }

    private void SaveFile(Stream stream, FileStream fs) {
        var buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0) {
            fs.Write(buffer, 0, bytesRead);
        }
    }

    public bool IsReusable {
        get {
            return false;
        }
    }
}

Таким образом, получается в 14 строке клиенту “говорится”, что часть файла успешно сохранена и это означает, что можно отправлять следующий.

Единственное, что надо упомянуть, так это то, что максимально допустимый размер файла для загрузки на сервер можно установить в файле конфигурации (web.config):

<configuration>
  <system.web>
    <httpRuntime maxRequestLength="xxx" />
  </system.web>
</configuration>

А если быть до конца честным, то в настоящий момент существует огромное количество бесплатных компонентов и контролов сторонних разработчиков, которые максимально просто и помогают решить данную проблему. Например, AjaxControlToolkit.

Комментарии к статье (4)

10.10.2013 0:06:37 Евгений

Не понял..., а где про upload (разумеется тоже больших файлов) :)

31.05.2017 11:11:15 Calabonga

Спасибо, Евгений, я дописал статью.

10.10.2013 10:30:24 Денис

Поправьте листинг, опечатка: Resoinse.Close();.
И небольшой комментарий к последнему абзацу.
Такие компоненты, как AjaxControlToolkit, помогают решить проблему с загрузкой файлов без перезагрузки страницы, но или проконтроллировать размер загружаемого контента перед загрузкой.
Интересное решение есть в MojoPortal для загрузки файлов: как для загрузки, так и для отображения процесса загрузки файлов.
А так в целом тема актуальная и интересная:-)

16.10.2013 22:03:48 Евгений

Ну в том то и дело, что посредством  asp.net напрямую (без предварительной фрагментации и её обработки), большой файл на сервер не зальёш, тогда как другие (т.е. которые не аспнет :) ) сие позволяют, на ютюб по моему было можно просто посредством бравзера залить большой файл , да я понимаю недостатки - что если в конце соединение навернётся, то выполнять заново и т.д. - но всё же...

Посему вариант токо юзать сторонние натив модули к iis, которые позволяют обрабатывать входящий поток не загоняя его в память

Если не прав поправьте...