Come posso generare al meglio un CSV (file di testo delimitato da virgole) per il download con ASP.NET?

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

  •  09-06-2019
  •  | 
  •  

Domanda

Questo è quello che ho.Funziona.Ma esiste un modo più semplice o migliore?

In una pagina ASPX, ho il collegamento per il download...

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

E poi ho il codice Download.aspx.vb dietro...

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
È stato utile?

Soluzione

La formattazione CSV presenta alcuni trucchi.Ti sei posto queste domande:

  • Qualcuno dei miei dati contiene virgole incorporate?
  • Qualcuno dei miei dati contiene virgolette doppie incorporate?
  • Qualcuno dei miei dati ha caratteri di ritorno a capo?
  • È necessario supportare le stringhe Unicode?

Vedo diversi problemi nel tuo codice sopra.La questione della virgola, prima di tutto...stai eliminando le virgole:

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

Perché?In CSV, dovresti racchiudere tutto ciò che contiene virgole tra virgolette:

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

(o qualcosa di simile...il mio VB non è molto buono).Se non sei sicuro che ci siano delle virgole, racchiudile tra virgolette.Se nella stringa sono presenti virgolette, è necessario evitarle raddoppiandole. " diventa "".

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

Poi, se vuoi, racchiudilo tra virgolette.Se nella stringa sono presenti caratteri di fine riga, è necessario racchiudere la stringa tra virgolette...sì, newline letterali Sono consentito nei file CSV.Sembra strano agli umani, ma va tutto bene.

Un buon scrittore CSV richiede una certa riflessione.Un buon lettore CSV (parser) è semplicemente difficile (e no, le espressioni regolari non sono abbastanza buone per analizzare CSV...ti porterà solo al 95% circa del percorso).

E poi c'è Unicode...o più in generale tematiche I18N (Internazionalizzazione).Ad esempio, stai eliminando le virgole da un prezzo formattato.Ma ciò presuppone che il prezzo sia formattato come previsto negli Stati Uniti.In Francia, la formattazione dei numeri è invertita (si utilizzano punti al posto delle virgole e viceversa).In conclusione, utilizza la formattazione indipendente dalla cultura ove possibile.

Mentre il problema è qui generando CSV, inevitabilmente dovrai analizzare CSV.In .NET, il miglior parser che ho trovato (gratuitamente) è Lettore CSV veloce SU CodiceProgetto.In realtà l'ho usato nel codice di produzione ed è davvero molto veloce e molto facile da usare!

Altri suggerimenti

Passo tutti i miei dati CSV attraverso una funzione come questa:

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

Inoltre, se non stai fornendo HTML, probabilmente vorrai un gestore http (.asHx file) anziché una pagina Web completa.Se crei un nuovo gestore in Visual Studio, è probabile che potresti semplicemente copiare il codice esistente nel metodo principale e funzionerà, con un piccolo aumento delle prestazioni per i tuoi sforzi.

Puoi creare l'equivalente di bookString() nella query stessa.Ecco quello che penso sarebbe un modo più semplice.

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

Se desideri un convertitore di valori delimitati da due punti, esiste un open source di terze parti chiamato FileHelper.Non sono sicuro della licenza open source con cui sia, ma mi ha aiutato parecchio.

C'è molto sovraccarico associato alla classe Page.Dato che stai semplicemente sputando un file CSV e non hai bisogno di postback, controlli server, memorizzazione nella cache o tutto il resto, dovresti trasformarlo in un gestore con un'estensione .ashx. Vedere qui.

Oltre a ciò che ha detto Simon, potresti voler leggere il Guida pratica CSV e assicurati che il tuo output non incontri nessuno dei trucchi.

Per chiarire qualcosa, Simon ha detto:

Poi, se vuoi, racchiudilo tra virgolette

I campi che contengono virgolette doppie ("") dovranno essere completamente racchiusi tra virgolette doppie.Non dovrebbe esserci alcun danno nel racchiudere semplicemente tutti i campi tra virgolette doppie, a meno che tu non voglia specificatamente che il parser elimini gli spazi bianchi iniziali e finali (invece di tagliarli tu stesso).

Utilizzo il seguente metodo quando creo un file CSV da un DataTable.ControllerContext è solo l'oggetto del flusso di risposta in cui viene scritto il file.Per te sarà solo l'oggetto 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;
        }

Sembra per lo più buono, tranne che nella tua funzione "BookString()" dovresti prima passare tutte quelle stringhe attraverso una piccola funzione come questa:

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 funzione formatterà bene le tue stringhe per CSV.Sostituisce le virgolette con virgolette doppie e aggiunge virgolette attorno alla stringa se nella stringa sono presenti virgolette o virgole.

Tieni presente che non tiene conto dei ritorni a capo, ma può garantire in modo sicuro solo un buon output CSV per quelle stringhe che sai essere prive di ritorni a capo (input da semplici moduli di testo di una riga, ecc.).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top