在C#中创建一个.csv文件

8

好的,我希望你能在C#中创建一个.csv文件。我一直在寻找资料,发现很多人都在使用system.IO.memorystream和system.io.streamwriter。

问题是:我有一个Web应用程序。我想让用户能够导出到Excel。问题是,Excel不能安装在服务器上(不要问为什么)。我想能够为报告编写一个.csv表格导出。现在,所有报告的标题和数据都将不同(通过循环解决此问题)。有没有人有示例或更好的资源供我查阅?


阅读了一些资料后,我只需要循环遍历并输出数据,用逗号分隔每个数据,在行末添加/n,直到完成循环...将文件保存为.csv格式,然后关闭流...是这样吗? - Tom
您还需要设置内容类型,如果某个数据可能包含",或换行字符,则也需要对其进行转义。相关代码请查看我的答案。 - Jon Hanna
7个回答

15

这是我通常采取的方法。虽然可能不是最有效的方法。

        /// <summary>
    /// Generates the contents of the log file.
    /// </summary>
    /// <returns>The contents of the log file.</returns>
    internal string GenerateLogFile()
    {
        StringBuilder csvExport = new StringBuilder();
        csvExport.AppendLine(Resources.CSVHeader);

        foreach (DataRow row in this.logEntries.Rows)
        {
            csvExport.AppendLine(
                string.Format(
                "\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\", \"{9}\"",
                row[ColumnNames.LogTime], row[ColumnNames.Field1], row[ColumnNames.Field2], row[ColumnNames.Field3], row[ColumnNames.Field4], row[ColumnNames.Field5], row[ColumnNames.Field6], row[ColumnNames.Field7], row[ColumnNames.Field8], row[ColumnNames.Field9]));
        }

        return csvExport.ToString();
    }

    /// <summary>
    /// Adds the CSV file to the response.
    /// </summary>
    /// <param name="csvExportContents">The contents of the CSV file.</param>
    internal void DisplayLogFile(string csvExportContents)
    {
        byte[] data = new ASCIIEncoding().GetBytes(csvExportContents);

        HttpContext.Current.Response.Clear();
        HttpContext.Current.Response.ContentType = "APPLICATION/OCTET-STREAM";
        HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment; filename=Export.csv");
        HttpContext.Current.Response.OutputStream.Write(data, 0, data.Length);
        HttpContext.Current.Response.End();
    }

1
CSVHeader来自哪里? - RealityDysfunction
我不明白可能包含 " 的值会如何被转义。 - Jon Hanna
@RealityDysfunction 我猜这是从一个 resx 文件中提取的。为了更好地说明问题,最好直接使用字符串字面值。 - MrBoJangles

11
private void WriteItem<T>(StreamWriter sr, T item)
{
    string itemString = item.ToString();
    if(itemString.IndexOfAny(new char[] { '"', ',', '\n', '\r' }) != -1)//skip test and always escape for different speed/filesize optimisation
    {
        sr.Write('"');
        sr.Write(itemString.Replace("\"", "\"\""));
        sr.Write('"');
    }
    else
        sr.Write(itemString);
}
private void WriteLine<T>(StreamWriter sr, IEnumerable<T> line)
{
    bool first = true;
    foreach(T item in line)
    {
        if(!first)
            sr.Write(',');
        first = false;
        WriteItem(sr, item);
    }
}
private void WriteCSV<T>(StreamWriter sr, IEnumerable<IEnumerable<T>> allLines)
{
    bool first = true;
    foreach(IEnumerable<T> line in allLines)
    {
        if(!first)
            sr.Write('\n');
        first = false;
        WriteLine(sr, line);
    }
}
private void WriteCSV<T>(HttpResponse response, IEnumerable<IEnumerable<T>> allLines)
{
    response.ContentType = "text/csv";
    WriteCSV(response.Output, allLines);
}

在发送文件时,建议同时发送content-disposition头部,并指定推荐的文件名。

编辑:最近,在需要在枚举项之间插入操作(例如上面的逗号和换行符)的情况下,我更喜欢直接处理枚举器,然后将第一个元素与其余元素分开处理,而不是保持一个不断被检查的布尔值。我开始将此视为一种微小的优化,但现在发现这更好地表达了不同于第一个元素的代码路径。因此,我现在会将上述代码编写为:

private void WriteLine<T>(StreamWriter sr, IEnumerable<T> line)
{
  using(var en = line.GetEnumerator())
  if(en.MoveNext())
  {
    WriteItem(sr, en.Current);
    while(en.MoveNext())
    {
      sr.Write(',');
      WriteItem(sr, en.Current);
    }
}
private void WriteCSV<T>(StreamWriter sr, IEnumerable<IEnumerable<T>> allLines)
{
    using(var en = allLines.GetEnumerator())
    if(en.MoveNext())
    {
      WriteLine(sr, en.Current);
      while(en.MoveNext())
      {
        sr.Write('\n');
        WriteLine(sr, en.Current);
      }
    }
}

实际使用例子会更好。 - T.Todua

4
你应该能够很好地使用使用System.IO.MemoryStreamSystem.IO.StreamWriter的示例。
不需要将MemoryStream写入文件,而是在ASP.NET中将其作为ResponseStream返回(确保设置适当的标头值,以便浏览器知道正在下载文件)。

4

仅在使用Office Interop时需要Excel。使用StringWriter,您只需创建一个文件并添加一些响应信息即可。只有在打开文件时才需要Excel。


3
创建CSV文件并没有什么魔法,唯一的问题在于CSV文件没有一个真正的标准,大多数导入程序只实现了一种特定的行为,如果你幸运的话,你会得到一个相对准确的猜测结果。
生成CSV文件只是打印行的问题。至于实际细节,请熟悉以下内容:http://en.wikipedia.org/wiki/Comma-separated_values 话虽如此,如果您想要导出到Excel,可以使用Microsoft的OOXML SDK:http://msdn.microsoft.com/en-us/library/bb448854.aspx OOXML SDK是一个C#库,您可以在服务器上使用它来生成Excel文件。Brian Jones博客是使用此库的一个很好的资源:http://blogs.msdn.com/b/brian_jones/

我需要在服务器上安装Excel吗? - Tom
您不需要在服务器上安装Excel即可运行OOXML SDK。事实上,您甚至可以在使用Mono的Linux系统上运行它。 - miguel.de.icaza

3
如果您不介意使用第三方库并且LGPL许可证在您的项目中是可以接受的,那么FileHelpers是一个很好的工具。

1
我喜欢FileHelpers!这个简单而快速的库使得将平面文件转换为对象变得容易。它还可以以面向对象的方式将对象转换为平面文本文件。而且它很快,还有一个向导来帮助指导你。干杯! - John C

2
这是用于创建CSV文件的函数:
 private async Task<string> WriteCSV<ViewModel>(IEnumerable<ViewModel> viewModels, string path)
            {
                Type itemType = typeof(ViewModel);
                var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                    .OrderBy(p => p.Name);

                var blobName = string.Empty;

                using (var ms = new MemoryStream())
                {

                    using (var writer = new System.IO.StreamWriter(ms))
                    {
                        writer.WriteLine(string.Join(", ", props.Select(p => p.Name)));

                        foreach (var item in viewModels)
                        {

                            writer.WriteLine(string.Join(", ", props.Select(p => p.GetValue(item, null))));
                        }
    }

 }
    }

否则,您可以参考下面的链接:

https://travelcodingnlotsmore.wordpress.com/2013/06/06/creating-csv-file-from-data-in-list-object-in-c/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接