关于vb.net:如何最好地生成一个CSV(逗号分隔的文本文件)以用于ASP.NET下载?

关于vb.net:如何最好地生成一个CSV(逗号分隔的文本文件)以用于ASP.NET下载?

How do I best generate a CSV (comma-delimited text file) for download with ASP.NET?

这就是我所拥有的。有用。但是,有没有更简单或更完善的方法呢?

一个ASPX页面,我有下载链接...

1
Download as CSV file</asp:HyperLink>

然后我就获得了Download.aspx.vb代码...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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

CSV格式有一些陷阱。您是否问过自己以下问题:

  • 我的任何数据中都包含逗号吗?
  • 我的数据中是否有任何嵌入的双引号?
  • 我的任何数据都有换行符吗?
  • 我需要支持Unicode字符串吗?

我在上面的代码中看到了几个问题。首先是逗号...您要剥离逗号:

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

为什么?在CSV中,您应该将所有带有引号的逗号括起来:

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

(或类似的东西...我的VB不太好)。如果您不确定是否有逗号,请在其周围加上引号。如果字符串中包含引号,则需要通过将它们加倍来对其进行转义。 "变为""

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

然后根据需要用引号将其引起来。如果您的字符串中包含换行符,则需要用引号将字符串引起来...是的,CSV文件中允许使用文字换行符。对人类来说看起来很奇怪,但这一切都很好。

一个好的CSV编写器需要一些思考。优秀的CSV阅读器(解析器)非常简单(不,正则表达式不足以解析CSV ...它只会为您提供95%的解析度)。

然后是Unicode ...或更普遍的是I18N(国际化)问题。例如,您要从格式化的价格中除去逗号。但这是假设价格的格式符合您在美国的预期。在法国,数字格式是相反的(使用句号代替逗号,反之亦然)。底线是,尽可能使用与文化无关的格式。

虽然这里的问题是生成CSV,但您不可避免地需要解析CSV。在.NET中,我发现的(免费的)最佳解析器是CodeProject上的Fast CSV Reader。我实际上已经在生产代码中使用了它,它确实非常快,而且非常易于使用!


我通过以下函数传递所有CSV数据:

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

此外,如果您不提供html,则可能需要使用http处理程序(.ashx文件)而不是完整的网页。如果您在Visual Studio中创建新的处理程序,则可能只是将现有代码复制到main方法中,这样就可以了,对您的工作而言,性能会有一点提升。


您可以在查询本身中创建等效于bookString()的内容。我认为这是一种更简单的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
    }
}

如果要用冒号分隔的值转换器,则有一个名为FileHelpers的第三方开源软件。我不确定它所使用的开源许可证是什么,但是它对我有很大帮助。


Page类有很多开销。由于您只是吐出CSV文件,并且不需要回发,服务器控件,缓存或其他文件,因此应将其设置为扩展名为.ashx的处理程序。参见此处。


除了Simon所说的以外,您可能还想阅读CSV指南,并确保您的输出不会跨任何陷阱。

为弄清西蒙说的话:

Then surround this by quotes if you want

包含双引号双引号(")的字段将需要用双引号完全包围。仅用双引号包装所有字段就不会有任何危害,除非您特别希望解析器将前导和尾随空格(而不是自己修剪)。


除了在您的函数" BookString()"中看起来最好之外,您应该首先通过这样的小函数传递所有这些字符串:

1
2
3
4
5
6
7
8
9
10
11
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)) +",")

该函数将为CSV格式正确设置字符串。如果字符串中有引号或逗号,它将用双引号替换引号,并在字符串周围添加引号。

请注意,它不考虑换行符,但只能安全地保证那些您知道不含换行符的字符串(来自简单的单行文本形式的输入等)的CSV输出良好。


当从DataTable构建CSV文件时,我使用以下方法。 ControllerContext只是将文件写入其中的响应流对象。对您来说,它将只是Response对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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("\
\
"
,"").Replace("\
"
,"").Replace("\
"
,"") +""";
            return output;
        }

推荐阅读