¿Cuál es la mejor manera de generar un CSV (archivo de texto delimitado por comas) para descargar con ASP.NET?

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

  •  09-06-2019
  •  | 
  •  

Pregunta

Esto es lo que tengo.Funciona.Pero, ¿existe una forma mejor o más sencilla?

En una página ASPX, tengo el enlace de descarga...

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

Y luego tengo el código subyacente 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
¿Fue útil?

Solución

El formato CSV tiene algunos inconvenientes.¿Te has hecho estas preguntas?

  • ¿Alguno de mis datos tiene comas incrustadas?
  • ¿Alguno de mis datos tiene comillas dobles incrustadas?
  • ¿Alguno de mis datos tiene nuevas líneas?
  • ¿Necesito admitir cadenas Unicode?

Veo varios problemas en su código anterior.Lo de la coma primero que nada...estás quitando comas:

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

¿Por qué?En CSV, todo lo que tenga comas debe estar entre comillas:

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

(o algo así...mi VB no es muy bueno).Si no está seguro de si hay comas, colóquelas entre comillas.Si hay comillas en la cadena, debe evitarlas duplicándolas. " se convierte "".

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

Luego rodee esto entre comillas si lo desea.Si hay nuevas líneas en su cadena, debe rodear la cadena entre comillas...sí, nuevas líneas literales son permitido en archivos CSV.A los humanos les parece extraño, pero todo está bien.

Un buen escritor CSV requiere algo de reflexión.Un buen lector (analizador) de CSV es simplemente difícil (y no, la expresión regular no es lo suficientemente buena para analizar CSV...sólo te llevará aproximadamente el 95% del camino).

Y luego está Unicode...o más generalmente cuestiones I18N (Internacionalización).Por ejemplo, está eliminando las comas de un precio formateado.Pero eso suponiendo que el precio tenga el formato esperado en EE. UU.En Francia, el formato de los números se invierte (se utilizan puntos en lugar de comas y viceversa).En pocas palabras, utilice un formato independiente de la cultura siempre que sea posible.

Si bien el problema aquí es generando CSV, inevitablemente necesitarás analizar CSV.En .NET, el mejor analizador que he encontrado (gratis) es Lector CSV rápido en Proyecto de código.De hecho, lo he usado en código de producción y es realmente rápido y muy fácil de usar.

Otros consejos

Paso todos mis datos CSV a través de una función como esta:

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

Además, si no está publicando html, probablemente desee un controlador http (.ashx archivo) en lugar de una página web completa.Si crea un nuevo controlador en Visual Studio, lo más probable es que pueda simplemente copiar el código existente en el método principal y simplemente funcionará, con un pequeño aumento de rendimiento para sus esfuerzos.

Puede crear el equivalente de bookString() en la propia consulta.Esta es la que creo que sería una forma más sencilla.

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 desea un convertidor de valores delimitados por dos puntos, existe un código abierto de terceros llamado Ayudantes de archivos.No estoy seguro de bajo qué licencia de código abierto se encuentra, pero me ha ayudado bastante.

Hay muchos gastos generales asociados con la clase Página.Dado que simplemente está escupiendo un archivo CSV y no necesita devolución de datos, controles de servidor, almacenamiento en caché ni el resto, debe convertirlo en un controlador con una extensión .ashx. Mira aquí.

Además de lo que dijo Simon, quizás quieras leer el Guía práctica de CSV y asegúrese de que su salida no se encuentre con ninguno de los errores.

Para aclarar algo dijo Simón:

Luego rodee esto entre comillas si lo desea.

Los campos que contienen comillas dobles ("") deberán estar completamente entre comillas dobles.No debería haber ningún problema en simplemente envolver todos los campos entre comillas dobles, a menos que desee específicamente que el analizador elimine los espacios en blanco iniciales y finales (en lugar de recortarlos usted mismo).

Utilizo el siguiente método al crear un archivo CSV a partir de una tabla de datos.ControllerContext es solo el objeto de flujo de respuesta donde se escribe el archivo.Para usted será simplemente el objeto Respuesta.

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

Se ve bastante bien, excepto en su función "BookString()", primero debe pasar todas esas cadenas a través de una pequeña función como esta:

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 función formateará bien sus cadenas para CSV.Reemplaza las comillas con comillas dobles y agrega comillas alrededor de la cadena si hay comillas o comas en la cadena.

Tenga en cuenta que no tiene en cuenta las nuevas líneas, pero solo puede garantizar de forma segura una buena salida CSV para aquellas cadenas que sabe que están libres de nuevas líneas (entradas de formularios de texto simples de una línea, etc.).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top