Вызов Response.TransmitFile () из статического метода - PullRequest
3 голосов
/ 16 февраля 2010

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

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 (и веб-программированием в целом), поэтому яЯ уверен, что я что-то здесь упускаю.Может ли кто-нибудь объяснить поведение, которое я наблюдаю, и предложить разумную альтернативу этому подходу?

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

РЕДАКТИРОВАТЬ 2: мне просто нужно было вызвать Response.Flush (), прежде чем я удалил файл.Смотрите мой ответ ниже.Спасибо!

Ответы [ 5 ]

5 голосов
/ 17 февраля 2010

Проблема заключалась в том, что временный файл удалялся перед отправкой данных. Мне просто нужно было вызвать 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);  
    }
}
1 голос
/ 17 февраля 2010

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

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

1 голос
/ 16 февраля 2010

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

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

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);
    }
}

Видите ли вы какие-либо отличия от вашей реализации?

1 голос
/ 16 февраля 2010

Попробуйте, вы можете получить 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 голосов
/ 16 февраля 2010

Я бы использовал это вместо. Текущий 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...