2010-02-16 5 views
3

У меня есть несколько страниц, которые должны поддерживать экспорт данных в электронную таблицу Excel. Я могу генерировать файлы Excel просто отлично, но я пытаюсь понять, как абстрагироваться от этого поведения, поэтому его легко использовать повторно со всех страниц, где он мне нужен. Моя текущая идея заключается в том, чтобы использовать статический метод полезности следующим образом:Вызов Response.TransmitFile() из статического метода

public static void SendExcelFile(System.Web.UI.Page callingPage, string downloadFileName, List<List<string>> data, string worksheetTitle) 
{ 
    string tempFileName = Path.GetTempFileName(); 

    try 
    { 
     // Generate file using ExcelPackage 
     GenerateExcelDoc(tempFileName, data, worksheetTitle); 

     callingPage.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName); 
     callingPage.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 
     callingPage.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString()); 
     callingPage.Response.TransmitFile(tempFileName); 
    } 
    finally 
    { 
     //When this is removed, the method works as expected. 
     if (File.Exists(tempFileName)) 
      File.Delete(tempFileName); 
    } 
} 

Обработчика щелчок, когда я звоню SendExcelFile выглядит следующим образом:

protected void lnkExport_Click(object sender, EventArgs e) 
{ 
    List<List<string>> dataList = GatherDataForSpreadsheet(); 
    Utility.SendExcelFile(this, "fileNameForDownload.xlsx", dataList, "MyReports"); 
} 

Этот код прекрасно работает как метод экземпляра вызывающей страницы. Однако, как статический метод, он вообще не работает. Когда я нажимаю кнопку, которая вызывает это, браузер показывает анимацию загрузки неограниченно, но никогда не запрашивает загрузку файла.

Я очень новичок в ASP.NET (и в основном в веб-программировании), поэтому я уверен, что здесь что-то не хватает. Может ли кто-нибудь объяснить объяснение поведения, которое я вижу, и предложить разумную альтернативу этому подходу?

EDIT: Если я удалю вызов в File.Delete() в конце, метод работает так, как ожидалось. Выполняет ли Response.TransmitFile() асинхронную передачу?

EDIT 2: Мне просто нужно было вызвать Response.Flush(), прежде чем я удалю файл. См. Мой ответ ниже. Спасибо!

+1

Прошли ли вы через код в режиме отладки? Если да, можете ли вы сказать, где это время (или если он даже вводит этот метод)? Исправлен ли ваш метод SendExcelFile? Если да, добавьте трассировку стека исключений, которая может помочь нам разобраться в этом. О, и примерный вызов этого метода также будет полезен (возможно, ваш обработчик кликов). –

+0

Я отлаживал это. Метод SendExcelFile, похоже, выполняется нормально, а затем выходит, но браузер никогда не запрашивает загрузку файла. На вкладке в IE или Firefox показана анимация загрузки, и в нижней части браузера есть индикатор выполнения загрузки страницы. Сама страница остается отзывчивой. – Odrade

+0

Я мог бы отправить обработчик кликов, если вы хотите, но я сомневаюсь, что это было бы полезно. Короче говоря, это просто: var data = GenerateData(); Utility.SendExcelFile (данные); – Odrade

ответ

5

Проблема заключалась в том, что временный файл удалялся до того, как данные были отправлены вниз. Мне просто нужно было вызвать Response.Flush() следующим образом:

public static void SendExcelFile(System.Web.UI.Page callingPage, string downloadFileName, List<List<string>> data, string worksheetTitle) 
{ 
    string tempFileName = Path.GetTempFileName(); 

    try 
    { 
     // Generate file using ExcelPackage 
     GenerateExcelDoc(tempFileName, data, worksheetTitle); 

     callingPage.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName); 
     callingPage.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 
     callingPage.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString()); 
     callingPage.Response.TransmitFile(tempFileName); 
     callingPage.Response.Flush(); //This is what I needed 
    } 
    finally 
    { 
     if (File.Exists(tempFileName)) 
      File.Delete(tempFileName); 
    } 
} 
+0

Спасибо за вашу помощь. – Odrade

1

Попробуйте это, вы можете получить Request и Response непосредственно от HttpContext.Current:

public static void SendExcelFile(string downloadFileName, List<List<string>> data, string worksheetTitle) 
{ 
    var context = HttpContext.Current; 
    string tempFolder = context.Request.PhysicalApplicationPath + "temp"; 
    string tempFileName = tempFolder + "tempFileName.xlsx" 

    if (!Directory.Exists(tempFolder)) 
     Directory.CreateDirectory(tempFolder); 

    // Generate file using ExcelPackage 
    GenerateExcelDoc(tempFileName, data, worksheetTitle); 

    context.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName); 
    context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 
    context.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString()); 
    context.Response.TransmitFile(tempFileName); 

    File.Delete(tempFileName); 
} 

Другой альтернативой является базовым классом для ваших страниц, которые содержат этот метод, который может быть гораздо проще маршрутом. Вы страницы не должны наследовать от System.Web.UI.Page, они могут наследовать от чего-то другого, например:

public class BasePage : System.Web.UI.Page 
{ 
    public void SendExcelFile(string downloadFileName, List<List<string>> data, string worksheetTitle) 
    { 
     string tempFolder =Request.PhysicalApplicationPath + "temp"; 
     string tempFileName = tempFolder + "tempFileName.xlsx" 

     if (!Directory.Exists(tempFolder)) 
      Directory.CreateDirectory(tempFolder); 

     // Generate file using ExcelPackage 
     GenerateExcelDoc(tempFileName, data, worksheetTitle); 

     Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName); 
     Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 
     Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString()); 
     Response.TransmitFile(tempFileName); 

     File.Delete(tempFileName); 
    } 
} 

Затем на странице класс выглядит следующим образом:

public partial class MyPage : BasePage 
{ 
    //Stuff! 
} 
+0

Подход №1 дает тот же результат, что и мой код (время ожидания ответа). Использование базового класса выглядит как простая альтернатива, но мне все же хотелось бы понять, почему подход №1 не работает. – Odrade

0

Я бы использовал это вместо этого. Текущий HTTP-контекст будет доступен на каждой странице.

HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName); 
HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 
HttpContext.Current.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString()); 
HttpContext.Current.Response.TransmitFile(tempFileName); 
+0

Ох и используйте HttpContext.Current.Response.Clear(); первый – nbushnell

+0

Ник предложил это, но он, похоже, не работает. Это потому, что я звоню из статического класса? – Odrade

+0

Какая часть не работает? Разве это не компиляция или просто не дает вам выход. – nbushnell

1

Нам нужна дополнительная информация - то, что вы делаете, должно работать.

Я создал урезанную версию, которая просто отправляет копию страницы вызывающему клиенту и он работает, как ожидалось:

public class Utility { 
    // This just sends the client a copy of the calling page itself 
    public static void SendExcelFile(Page callingPage) { 
     string path = callingPage.Request.PhysicalPath; 
     callingPage.Response.AddHeader("Content-Disposition", "attachment;filename=test.xls"); 
     callingPage.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 
     callingPage.Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString()); 
     callingPage.Response.TransmitFile(path); 
    } 
} 

Вот мое призвание страницы:

public partial class main : System.Web.UI.Page { 
    protected void SendButton_Click(object sender, EventArgs e) {   
     Utility.SendExcelFile(this); 
    } 
} 

ли вам см. какие-либо отличия от вашей реализации?

+0

Хорошо, я пропустил некоторые важные детали. Позвольте мне взглянуть на это некоторое время. – Odrade

+0

Основное отличие - вызов File.Delete(). Если я удалю это, он будет работать нормально. Означает ли это, что Response.TransmitFile() не является синхронным? – Odrade

+0

Мне просто нужно было вызвать Response.Flush(), прежде чем я удалю файл. Спасибо за вашу помощь! – Odrade

1

В этот момент я бы использовал прокси-сервер отладки HTTP, например Fiddler, для сравнения сеансов HTTP, сгенерированных как рабочими (страницами кода), так и неработоспособными (статическими) версиями вашего кода.

В стороне, вы должны знать, что ваш код в письменной форме не будет работать, если несколько пользователей одновременно нажимают кнопку - файл временного файла первого пользователя может быть перезаписан вторым файлом пользователя, и файл второго пользователя может быть удален в середине передачи! Рассмотрите возможность использования Path.GetTempFileName() или руководства в имени файла, чтобы гарантировать, что файл каждого пользователя уникально назван.

+0

Согласен с комментариями Майка - вероятно, стоит также удалить файл в блоке 'finally'. –

+0

У меня на самом деле была логика использования текущей временной метки для создания уникальных имен файлов, но я удалил ее из кода примера. Path.GetTempFileName() - хороший совет. – Odrade

+0

Также полезно, если есть опасения, что вредоносные пользователи могут загружать временные файлы других людей - если ваши временные файлы хранятся в формате ~/temp /, вы должны убедиться, что вы настроили IIS, чтобы запретить доступ к этому папка. –

Смежные вопросы