Quelle est la meilleure façon de générer un CSV (fichier texte délimité par des virgules) à télécharger avec ASP.NET ?

StackOverflow https://stackoverflow.com/questions/44194

  •  09-06-2019
  •  | 
  •  

Question

C'est ce que j'ai.Ça marche.Mais existe-t-il un moyen plus simple ou meilleur ?

Sur une page ASPX, j'ai le lien de téléchargement...

<asp:HyperLink ID="HyperLinkDownload" runat="server" NavigateUrl="~/Download.aspx">Download as CSV file</asp:HyperLink>

Et puis j'ai le code derrière Download.aspx.vb...

Public Partial Class Download
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        'set header
        Response.Clear()
        Response.ContentType = "text/csv"
        Dim FileName As String = "books.csv"
        Response.AppendHeader("Content-Disposition", "attachment;filename=" + FileName)

        'generate file content
        Dim db As New bookDevelopmentDataContext
        Dim Allbooks = From b In db.books _
                       Order By b.Added _
                       Select b
        Dim CsvFile As New StringBuilder
        CsvFile.AppendLine(CsvHeader())
        For Each b As Book In Allbooks
            CsvFile.AppendLine(bookString(b))
        Next

        'write the file
        Response.Write(CsvFile.ToString)
        Response.End()
    End Sub

    Function CsvHeader() As String
        Dim CsvLine As New StringBuilder
        CsvLine.Append("Published,")
        CsvLine.Append("Title,")
        CsvLine.Append("Author,")
        CsvLine.Append("Price")
        Return CsvLine.ToString
    End Function

    Function bookString(ByVal b As Book) As String
        Dim CsvLine As New StringBuilder
        CsvLine.Append(b.Published.ToShortDateString + ",")
        CsvLine.Append(b.Title.Replace(",", "") + ",")
        CsvLine.Append(b.Author.Replace(",", "") + ",")
        CsvLine.Append(Format(b.Price, "c").Replace(",", ""))
        Return CsvLine.ToString
    End Function

End Class
Était-ce utile?

La solution

Le formatage CSV comporte quelques pièges.Vous êtes-vous posé ces questions :

  • Certaines de mes données contiennent-elles des virgules ?
  • Certaines de mes données comportent-elles des guillemets doubles intégrés ?
  • Certaines de mes données comportent-elles des sauts de ligne ?
  • Dois-je prendre en charge les chaînes Unicode ?

Je vois plusieurs problèmes dans votre code ci-dessus.Le truc de la virgule tout d'abord...tu supprimes les virgules :

CsvLine.Append(Format(b.Price, "c").Replace(",", ""))

Pourquoi?En CSV, vous devez entourer tout ce qui comporte des virgules de guillemets :

CsvLine.Append(String.Format("\"{0:c}\"", b.Price))

(ou quelque chose comme ça...mon VB n'est pas très bon).Si vous n'êtes pas sûr qu'il y ait des virgules, mettez-les entre guillemets.S'il y a des guillemets dans la chaîne, vous devez les échapper en les doublant. " devient "".

b.Title.Replace("\"", "\"\"")

Ensuite, entourez-le de guillemets si vous le souhaitez.S'il y a des nouvelles lignes dans votre chaîne, vous devez entourer la chaîne de guillemets...oui, des nouvelles lignes littérales sont autorisés dans les fichiers CSV.Cela semble bizarre aux humains, mais tout va bien.

Un bon rédacteur CSV nécessite une certaine réflexion.Un bon lecteur (analyseur) CSV est tout simplement difficile (et non, les expressions régulières ne sont pas assez bonnes pour analyser le CSV...cela ne vous mènera qu'à environ 95 % du chemin).

Et puis il y a Unicode...ou plus généralement les problématiques I18N (Internationalisation).Par exemple, vous supprimez les virgules d’un prix formaté.Mais cela suppose que le prix soit formaté comme prévu aux États-Unis.En France, le formatage des nombres est inversé (points utilisés à la place des virgules, et vice versa).En fin de compte, utilisez autant que possible un formatage indépendant de la culture.

Bien que le problème ici soit générateur CSV, vous devrez inévitablement analyser CSV.Dans .NET, le meilleur analyseur que j'ai trouvé (gratuitement) est Lecteur CSV rapide sur CodeProjet.Je l'ai en fait utilisé dans le code de production et c'est vraiment très rapide et très facile à utiliser !

Autres conseils

Je transmets toutes mes données CSV via une fonction comme celle-ci :

Function PrepForCSV(ByVal value As String) As String
    return String.Format("""{0}""", Value.Replace("""", """"""))
End Function

De plus, si vous ne proposez pas de HTML, vous aurez probablement besoin d'un gestionnaire http (.ashx) plutôt qu'une page Web complète.Si vous créez un nouveau gestionnaire dans Visual Studio, il y a de fortes chances que vous puissiez simplement copier votre code existant dans la méthode principale et cela fonctionnera, avec une petite amélioration des performances pour vos efforts.

Vous pouvez créer l'équivalent de bookString() dans la requête elle-même.Voici ce que je pense être un moyen plus simple.

protected void Page_Load(object sender, EventArgs e)
{
    using (var db = new bookDevelopmentDataContext())
    {
        string fileName = "book.csv";
        var q = from b in db.books
                select string.Format("{0:d},\"{1}\",\"{2}\",{3:F2}", b.Published, b.Title.Replace("\"", "\"\""), b.Author.Replace("\"", "\"\""), t.price);

        string outstring = string.Join(",", q.ToArray());

        Response.Clear();
        Response.ClearHeaders();
        Response.ContentType = "text/csv";
        Response.AppendHeader("Content-Disposition", string.Format("attachment;filename={0}", fileName));
        Response.Write("Published,Title,Author,Price," + outstring);
        Response.End();
    }
}

Si vous souhaitez un convertisseur de valeurs délimitées par deux points, il existe un open source tiers appelé Aides-fichiers.Je ne suis pas sûr de la licence open source sous laquelle il se trouve, mais cela m'a beaucoup aidé.

Il y a beaucoup de surcharge associée à la classe Page.Puisque vous crachez simplement un fichier CSV et que vous n'avez pas besoin de publication, de contrôles de serveur, de mise en cache ou du reste, vous devriez en faire un gestionnaire avec une extension .ashx. Vois ici.

En plus de ce que Simon a dit, vous voudrez peut-être lire le Guide pratique CSV et assurez-vous que votre sortie ne rencontre aucun des pièges.

Pour clarifier quelque chose, Simon a dit :

Ensuite entourez ceci de guillemets si vous le souhaitez

Les champs contenant des guillemets doubles ("") devront être entièrement entourés de guillemets doubles.Il ne devrait y avoir aucun mal à simplement envelopper tous les champs avec des guillemets doubles, à moins que vous souhaitiez spécifiquement que l'analyseur supprime les espaces de début et de fin (au lieu de les couper vous-même).

J'utilise la méthode suivante lors de la création d'un fichier CSV à partir d'un DataTable.ControllerContext est simplement l'objet de flux de réponse dans lequel le fichier est écrit.Pour vous, ce sera simplement l'objet Response.

public override void ExecuteResult(ControllerContext context)
        {
            StringBuilder csv = new StringBuilder(10 * Table.Rows.Count * Table.Columns.Count);

            for (int c = 0; c < Table.Columns.Count; c++)
            {
                if (c > 0)
                    csv.Append(",");
                DataColumn dc = Table.Columns[c];
                string columnTitleCleaned = CleanCSVString(dc.ColumnName);
                csv.Append(columnTitleCleaned);
            }
            csv.Append(Environment.NewLine);
            foreach (DataRow dr in Table.Rows)
            {
                StringBuilder csvRow = new StringBuilder();
                for(int c = 0; c < Table.Columns.Count; c++)
                {
                    if(c != 0)
                        csvRow.Append(",");

                    object columnValue = dr[c];
                    if (columnValue == null)
                        csvRow.Append("");
                    else
                    {
                        string columnStringValue = columnValue.ToString();


                        string cleanedColumnValue = CleanCSVString(columnStringValue);

                        if (columnValue.GetType() == typeof(string) && !columnStringValue.Contains(","))
                        {
                            cleanedColumnValue = "=" + cleanedColumnValue; // Prevents a number stored in a string from being shown as 8888E+24 in Excel. Example use is the AccountNum field in CI that looks like a number but is really a string.
                        }
                        csvRow.Append(cleanedColumnValue);
                    }
                }
                csv.AppendLine(csvRow.ToString());
            }

            HttpResponseBase response = context.HttpContext.Response;
            response.ContentType = "text/csv";
            response.AppendHeader("Content-Disposition", "attachment;filename=" + this.FileName);
            response.Write(csv.ToString());
        }

        protected string CleanCSVString(string input)
        {
            string output = "\"" + input.Replace("\"", "\"\"").Replace("\r\n", " ").Replace("\r", " ").Replace("\n", "") + "\"";
            return output;
        }

Cela semble plutôt bien, sauf dans votre fonction "BookString()", vous devez d'abord transmettre toutes ces chaînes via une petite fonction comme celle-ci :

Private Function formatForCSV(stringToProcess As String) As String
    If stringToProcess.Contains("""") Or stringToProcess.Contains(",") Then
        stringToProcess = String.Format("""{0}""", stringToProcess.Replace("""", """"""))
    End If
    Return stringToProcess
End Function

'So, lines like this:
CsvLine.Append(b.Title.Replace(",", "") + ",")
'would be lines like this instead:
CsvLine.Append(formatForCSV(b.Title)) + ",")

La fonction formatera bien vos chaînes pour CSV.Il remplace les guillemets par des guillemets doubles et ajoute des guillemets autour de la chaîne s'il y a des guillemets ou des virgules dans la chaîne.

Notez qu'il ne prend pas en compte les nouvelles lignes, mais ne peut garantir en toute sécurité une bonne sortie CSV que pour les chaînes dont vous savez qu'elles sont exemptes de nouvelles lignes (entrées provenant de simples formulaires de texte d'une ligne, etc.).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top