Qual a melhor forma de gerar um CSV (arquivo de texto delimitado por vírgula) para download com ASP.NET?

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

  •  09-06-2019
  •  | 
  •  

Pergunta

Isto é o que eu tenho.Funciona.Mas existe uma maneira mais simples ou melhor?

Uma página ASPX, tenho o link para download ...

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

E então eu tenho o Code Behind 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
Foi útil?

Solução

A formatação CSV tem algumas dicas.Você já se fez estas perguntas:

  • Algum dos meus dados tem vírgulas incorporadas?
  • Algum dos meus dados contém aspas duplas incorporadas?
  • Algum dos meus dados possui novas linhas?
  • Preciso oferecer suporte a strings Unicode?

Vejo vários problemas no seu código acima.A coisa da vírgula antes de tudo...você está removendo vírgulas:

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

Por que?Em CSV, você deve colocar entre aspas tudo que tiver vírgulas:

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

(ou algo assim...meu VB não é muito bom).Se você não tiver certeza se há vírgulas, coloque aspas.Se houver aspas na string, você precisará escapar delas duplicando-as. " torna-se "".

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

Em seguida, coloque isso entre aspas, se desejar.Se houver novas linhas em sua string, você precisará colocá-la entre aspas...sim, novas linhas literais são permitido em arquivos CSV.Parece estranho para os humanos, mas está tudo bem.

Um bom redator de CSV requer um pouco de reflexão.Um bom leitor (analisador) de CSV é simplesmente difícil (e não, regex não é bom o suficiente para analisar CSV ...você só levará cerca de 95% do caminho até lá).

E depois há o Unicode...ou, mais genericamente, questões I18N (Internacionalização).Por exemplo, você está eliminando vírgulas de um preço formatado.Mas isso pressupõe que o preço esteja formatado conforme o esperado nos EUA.Na França, a formatação dos números é invertida (pontos são usados ​​em vez de vírgulas e vice-versa).Resumindo, use formatação independente de cultura sempre que possível.

Embora a questão aqui seja gerando CSV, inevitavelmente você precisará analisar o CSV.No .NET, o melhor analisador que encontrei (de graça) é Leitor CSV rápido sobre CódigoProjeto.Na verdade, usei-o em código de produção e é realmente muito rápido e muito fácil de usar!

Outras dicas

Passo todos os meus dados CSV por meio de uma função como esta:

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

Além disso, se você não estiver servindo HTML, provavelmente desejará um manipulador http (.ashx) em vez de uma página da web completa.Se você criar um novo manipulador no Visual Studio, provavelmente você poderá simplesmente copiar o código existente para o método principal e ele simplesmente funcionará, com um pequeno aumento de desempenho para seus esforços.

Você pode criar o equivalente a bookString() na própria consulta.Aqui está o que eu acho que seria uma maneira mais simples.

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 você deseja um conversor de valor delimitado por dois pontos, existe um código aberto de terceiros chamado Ajudantes de arquivos.Não tenho certeza sobre qual licença de código aberto está, mas me ajudou bastante.

Há muita sobrecarga associada à classe Page.Como você está apenas exibindo um arquivo CSV e não precisa de postback, controles de servidor, cache ou o resto, você deve transformá-lo em um manipulador com extensão .ashx. Veja aqui.

Além do que Simon disse, você pode querer ler o Guia prático de CSV e certifique-se de que sua saída não seja executada em nenhuma das pegadinhas.

Para esclarecer algo, Simon disse:

Em seguida, coloque isso entre aspas, se quiser

Os campos que contêm aspas duplas ("") precisarão ser completamente cercados por aspas duplas.Não deve haver nenhum mal em apenas colocar todos os campos entre aspas duplas, a menos que você queira especificamente que o analisador elimine os espaços em branco iniciais e finais (em vez de cortá-los você mesmo).

Eu uso o seguinte método ao criar um arquivo CSV a partir de um DataTable.ControllerContext é apenas o objeto de fluxo de resposta onde o arquivo é gravado.Para você, será apenas o objeto 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;
        }

Parecendo muito bom, exceto na sua função "BookString ()", você deve primeiro passar todas essas strings por uma pequena função 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)) + ",")

A função formatará bem suas strings para CSV.Ele substitui aspas por aspas duplas e adiciona aspas ao redor da string se houver aspas ou vírgulas na string.

Observe que ele não leva em conta novas linhas, mas só pode garantir com segurança uma boa saída CSV para aquelas strings que você sabe que estão livres de novas linhas (entradas de formulários de texto simples de uma linha, etc.).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top