2010-02-16 11 views
3

J'ai un certain nombre de pages qui doivent prendre en charge l'exportation de données vers une feuille de calcul Excel. Je peux très bien générer les fichiers Excel, mais j'essaie de trouver un moyen d'abstraire ce comportement pour qu'il soit facilement réutilisable à partir de toutes les pages où j'en ai besoin. Mon idée actuelle est d'utiliser une méthode utilitaire statique, comme suit:Appel de Response.TransmitFile() à partir de la méthode statique

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

Le gestionnaire de clic où je vous appelle SendExcelFile ressemble à ceci:

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

Ce code fonctionne très bien comme une méthode d'instance de la page d'appel. En tant que méthode statique, cependant, cela ne fonctionne pas du tout. Lorsque je clique sur le bouton qui l'invoque, le navigateur affiche indéfiniment les animations de chargement, mais ne demande jamais le téléchargement d'un fichier.

Je suis très nouveau sur ASP.NET (et la programmation web en général), donc je suis sûr qu'il me manque quelque chose ici. Quelqu'un pourrait-il expliquer le comportement que je vois et proposer une alternative raisonnable à cette approche?

EDIT: Si je supprime l'appel à File.Delete() à la fin, la méthode fonctionne comme prévu. Est-ce que Response.TransmitFile() effectue le transfert de manière asynchrone?

EDIT 2: J'ai juste besoin d'appeler Response.Flush() avant de supprimer le fichier. Voir ma réponse ci-dessous. Merci!

+1

Avez-vous parcouru le code en mode de débogage? Si oui, pouvez-vous dire à quel moment il arrive à expiration (ou s'il est même entré dans cette méthode)? Est-ce que votre méthode 'SendExcelFile' lève une exception? Si c'est le cas, veuillez ajouter la trace de pile de l'exception, ce qui pourrait nous aider à aller au fond des choses. Oh, et un exemple d'appel à cette méthode serait utile aussi (peut-être votre gestionnaire de clic). –

+0

J'ai débogué à travers elle. La méthode SendExcelFile semble s'exécuter normalement puis se ferme, mais le navigateur ne demande jamais le téléchargement du fichier. L'onglet dans IE ou Firefox montre l'animation de chargement, et il y a une barre de progression de chargement de la page au bas du navigateur. La page elle-même reste réactive. – Odrade

+0

Je pourrais poster le gestionnaire de clic si vous voulez, mais je doute que ce serait utile. En bref, c'est juste: var data = GenerateData(); Utility.SendExcelFile (données); – Odrade

Répondre

5

Le problème était que le fichier temporaire était en cours de suppression avant l'envoi des données. Je juste besoin d'appeler Response.Flush() comme si:

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

Merci pour votre aide tout le monde. – Odrade

1

Essayez, vous pouvez obtenir le Request et Response directement sur 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); 
} 

Une autre alternative est une classe de base pour vos pages qui contient cette méthode, qui peut être une voie beaucoup plus facile. Vous pages n'avez pas hériter de System.Web.UI.Page, ils peuvent hériter de quelque chose d'autre, comme celui-ci:

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

Ensuite, dans votre page la classe ressemble à:

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

L'approche n ° 1 donne le même résultat que mon code (attente de réponse). Utiliser une classe de base ressemble à une alternative facile, mais j'aimerais quand même comprendre pourquoi l'approche n ° 1 ne fonctionne pas. – Odrade

0

Je voudrais utiliser cela à la place. Le contexte HTTP actuel sera disponible sur chaque page.

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

Oh et utilisez HttpContext.Current.Response.Clear(); premier – nbushnell

+0

Nick a suggéré cela, mais cela ne semble pas fonctionner. Est-ce parce que je fais l'appel d'une classe d'utilité statique? – Odrade

+0

Quelle partie ne fonctionne pas? Ne compile-t-il pas ou ne vous donne-t-il pas la sortie. – nbushnell

1

Nous avons besoin de plus d'informations - ce que vous faites devrait fonctionner.

J'ai créé une version allégée qui envoie juste une copie de la page d'appel au client et il fonctionne comme prévu:

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

Voilà ma page d'appel:

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

-vous voir les différences de votre mise en œuvre?

+0

Ok, il me manque clairement des détails importants. Laissez-moi regarder pendant un moment. – Odrade

+0

La principale différence est l'appel File.Delete(). Si j'enlève ça, ça fonctionne normalement. Cela signifie-t-il que Response.TransmitFile() n'est pas synchrone? – Odrade

+0

J'ai juste besoin d'appeler Response.Flush() avant de supprimer le fichier. Merci de votre aide! – Odrade

1

À ce stade, j'utiliserais un proxy de débogage HTTP comme Fiddler pour comparer les sessions HTTP générées à la fois par les versions de travail (code codebehind) et non workwork (statique) de votre code.En outre, sachez que votre code tel qu'il est écrit ne fonctionnera pas bien si plusieurs utilisateurs cliquent sur le bouton en même temps - le fichier temporaire du premier utilisateur peut être écrasé par le fichier du second utilisateur, et le fichier du second utilisateur peut être supprimé en cours de transmission! Pensez à utiliser Path.GetTempFileName() ou un guid dans le nom de fichier pour vous assurer que le nom de chaque utilisateur est unique.

+0

D'accord avec les commentaires de Mike - il vaut probablement la peine de supprimer le fichier dans un bloc 'finally'. –

+0

J'ai effectivement eu une certaine logique pour utiliser l'horodatage actuel pour générer des noms de fichiers uniques, mais je l'ai retiré de l'exemple de code. Path.GetTempFileName() est un bon conseil. – Odrade

+0

Il est également utile de savoir si les utilisateurs malveillants peuvent télécharger les fichiers temporaires d'autres personnes - si vos fichiers temporaires sont stockés dans ~/temp /, vous devez vous assurer que vous avez configuré IIS pour refuser l'accès à cette dossier. –

Questions connexes