3 августа 2011 г.

Использование HttpHandler-a для скачивания (отдачи) файлов в ASP.Net MVC

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

<appSettings>
    .......
    <add key="FilesPath" value="~/Uploads/Files/" />
  </appSettings>

Дальше, как и в прошлый раз, создадим handler, с именем MyFile.ashx, и напишем код для работы с файлом:
   1:  public class MyFile : IHttpHandler
   2:  {
   3:   
   4:      public void ProcessRequest(HttpContext context)
   5:      {
   6:          if (context.Request.Params["name"] != null)
   7:          {
   8:              try
   9:              {
  10:                  string path = ConfigurationManager.AppSettings["FilesPath"] + "/" +
                        context.Request.Params["name"];
  11:                  string filePath = HttpContext.Current.Server.MapPath(path);
  12:                  FileInfo file = new System.IO.FileInfo(filePath);
  13:   
  14:                  context.Response.Clear();
  15:                  context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
  16:                  context.Response.Cache.SetNoStore();
  17:                  context.Response.Cache.SetExpires(DateTime.MinValue);
  18:                  context.Response.AddHeader("Content-Disposition", "myfile; filename=" +
                         context.Request.Params["name"]);
  19:                  context.Response.AddHeader("Content-Length", file.Length.ToString());
  20:                  context.Response.ContentType = GetFileContentType(file.Extension);
  21:                  context.Response.WriteFile(file.FullName);
  22:                  context.ApplicationInstance.CompleteRequest();
  23:                  context.Response.End();
  24:              }
  25:              catch (Exception)
  26:              {
  27:                  return;
  28:              }
  29:   
  30:          }
  31:   
  32:      }
  33:   
  34:      public bool IsReusable
  35:      {
  36:          get
  37:          {
  38:              return true;
  39:          }
  40:      }
  41:   
  42:      public static string GetFileContentType(string fileextension)
  43:      {
  44:          //set the default content-type
  45:          //const string DEFAULT_CONTENT_TYPE = "application/unknown";
  46:          const string DEFAULT_CONTENT_TYPE = "application/octet-stream";
  47:   
  48:          RegistryKey regkey, fileextkey;
  49:          string filecontenttype;
  50:   
  51:          try
  52:          {
  53:              //look in HKCR
  54:              regkey = Registry.ClassesRoot;
  55:   
  56:              //look for extension
  57:              fileextkey = regkey.OpenSubKey(fileextension);
  58:   
  59:              //retrieve Content Type value
  60:              filecontenttype =
                   fileextkey.GetValue("Content Type", DEFAULT_CONTENT_TYPE).ToString();
  61:   
  62:              //cleanup
  63:              fileextkey = null;
  64:              regkey = null;
  65:          }
  66:          catch
  67:          {
  68:              filecontenttype = DEFAULT_CONTENT_TYPE;
  69:          }
  70:   
  71:          //print the content type
  72:          return filecontenttype;
  73:   
  74:      }
  75:  }

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

Для того, чтобы отобразить все доступные файлы, мы соберем их имена в один список, который и передадим в представление. Все действия будут происходить в ActionResult Index(), контроллера HomeController:

   1:  List<string> filesList = new List<string>();
   2:   
   3:  string path = ConfigurationManager.AppSettings["FilesPath"];
   4:  string dir = Server.MapPath(path);
   5:  if (Directory.Exists(dir))
   6:  {
   7:      var di = new DirectoryInfo(dir);
   8:      var files = di.GetFiles();
   9:      if (files.Length > 0)
  10:      {
  11:          foreach (var f in files)
  12:          {
  13:              filesList.Add(f.Name);
  14:          }
  15:   
  16:          ViewData["fList"] = filesList;
  17:      }
  18:  }

Здесь все просто. Мы перебираем все файлы из нашего хранилища, и создаем List, содержащий их имена. Больше ничего для handler-а не будет нужно. После формирования списка, от будет передан в представление через ViewData.

Нам осталось только вывести в представлении Index.aspx список файлов-ссылок на скачиване. При этом, сформировать ссылку так, чтобы вызывался наш хэндлер с параметром-именем файла:

   1:  <%if (ViewData["fList"] != null)
   2:      {%>
   3:  <table>
   4:      <% var fList = ViewData["fList"] as List<string>;
   5:          foreach (var f in fList)
   6:          {%>
   7:      <tr>
   8:          <td>
   9:              <a href="/MyFile.ashx?&name=<%= f %>"><%= f %></a>
  10:          </td>
  11:      </tr>
  12:      <%} %>
  13:  </table>
  14:  <%}%>

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

Файлы успешно скачиваются. На этом все.
_____
Исходники