【问题标题】:unable to correctly export my data as csv file when the data contain embedded commas, inside my asp.net MVC当我的 asp.net MVC 中的数据包含嵌入式逗号时,无法将我的数据正确导出为 csv 文件
【发布时间】:2017-10-18 09:01:51
【问题描述】:

我正在我的 asp.net mvc 5 Web 应用程序中构建一个将我的数据导出为 CSV 文件的操作方法。 现在我尝试按照此链接中提到的方法https://www.codeproject.com/Articles/1078092/ASP-MVC-Export-download-Grid-contents-in-different 我在其中添加了以下类:-

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace MVCExport
{

    /// <summary>
    /// CSV file result impementation
    /// </summary>
    /// <typeparam name="TEntity">Entity list to transform to CSV</typeparam>
    public class CsvFileResult<TEntity> : FileResult where TEntity : class
    {
        #region Fields

        private const string DefaultContentType = "text/csv";
        private string _delimiter;
        private string _lineBreak;
        private Encoding _contentEncoding;
        private IEnumerable<string> _headers;
        private IEnumerable<PropertyInfo> _sourceProperties;
        private IEnumerable<TEntity> _dataSource;
        private Func<TEntity, IEnumerable<string>> _map;

        #endregion

        #region Properties

        public Func<TEntity, IEnumerable<string>> Map
        {
            get
            {

                return _map;
            }
            set { _map = value; }
        }
        public IEnumerable<TEntity> DataSource
        {
            get
            {
                return this._dataSource;
            }
        }
        /// <summary>
        /// CSV delimiter default ,
        /// </summary>
        public string Delimiter
        {
            get
            {
                if (string.IsNullOrEmpty(this._delimiter))
                {
                    this._delimiter = CultureInfo.CurrentCulture.TextInfo.ListSeparator;
                }

                return this._delimiter;
            }

            set { this._delimiter = value; }
        }

        /// <summary>
        /// Content Encoding (default is UTF8).
        /// </summary>
        public Encoding ContentEncoding
        {

            get
            {
                if (this._contentEncoding == null)
                {
                    this._contentEncoding = Encoding.Unicode;
                }

                return this._contentEncoding;
            }

            set { this._contentEncoding = value; }


        }

        /// <summary>
        /// the first line of the CSV file, column headers
        /// </summary>
        public IEnumerable<string> Headers
        {
            get
            {
                if (this._headers == null)
                {
                    this._headers = typeof(TEntity).GetProperties().Select(x => x.Name);
                }

                return this._headers;
            }

            set { this._headers = value; }
        }

        public IEnumerable<PropertyInfo> SourceProperties
        {
            get
            {
                if (this._sourceProperties == null)
                {
                    this._sourceProperties = typeof(TEntity).GetProperties();
                }

                return this._sourceProperties;
            }
        }



        /// <summary>
        ///  byte order mark (BOM)  .
        /// </summary>
        public bool HasPreamble { get; set; }

        /// <summary>
        /// Line  delimiter \n
        /// </summary>
        public string LineBreak
        {
            get
            {
                if (string.IsNullOrEmpty(this._lineBreak))
                {
                    this._lineBreak = Environment.NewLine;
                }

                return this._lineBreak;
            }

            set { this._lineBreak = value; }
        }



        /// <summary>
        /// Get or Set the response output buffer 
        /// </summary>
        public bool BufferOutput { get; set; }

        #endregion

        #region Ctor
        /// <summary>
        /// Creats new instance of CsvFileResult{TEntity}
        /// </summary>
        /// <param name="source">List of data to be transformed to csv</param>
        /// <param name="fileDonwloadName">CSV file name</param>
        /// <param name="contentType">Http response content type</param>
        public CsvFileResult(IEnumerable<TEntity> source, string fileDonwloadName, string contentType)
            : base(contentType)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            this._dataSource = source;

            if (string.IsNullOrEmpty(fileDonwloadName))
                throw new ArgumentNullException("fileDonwloadName");
            this.FileDownloadName = fileDonwloadName;

            this.BufferOutput = true;

        }

        /// <summary>
        /// Creats new instance of CsvFileResult{TEntity}
        /// </summary>
        /// <param name="source">List of data to be transformed to csv</param>
        /// <param name="fileDonwloadName">CSV file name</param>
        public CsvFileResult(IEnumerable<TEntity> source, string fileDonwloadName)
            : this(source, fileDonwloadName, DefaultContentType)
        {

        }

        /// <summary>
        /// Creats new instance of CsvFileResult{TEntity}
        /// </summary>
        /// <param name="source">List of data to be transformed to csv</param>
        /// <param name="fileDonwloadName">CSV file name</param>
        /// <param name="map">Custom transformation delegate</param>
        /// <param name="headers">Columns headers</param>
        public CsvFileResult(IEnumerable<TEntity> source, string fileDonwloadName, Func<TEntity, IEnumerable<string>> map, IEnumerable<string> headers)
            : this(source, fileDonwloadName, DefaultContentType)
        {
            this._headers = headers;
            this._map = map;
        }

        #endregion

        #region override

        protected override void WriteFile(HttpResponseBase response)
        {
            response.ContentEncoding = this.ContentEncoding;
            response.BufferOutput = this.BufferOutput;
            var streambuffer = ContentEncoding.GetBytes(this.GetCSVData());
            if (HasPreamble)
            {
                var preamble = this.ContentEncoding.GetPreamble();
                response.OutputStream.Write(preamble, 0, preamble.Length);
            }

            response.OutputStream.Write(streambuffer, 0, streambuffer.Length);
        }

        #endregion

        #region local routines

        private string GetCSVHeader()
        {
            string csv = "";
            csv = String.Join(this.Delimiter, this.Headers.Select(x => this.FormatCSV(x)));

            return csv;
        }


        private string GetCSVData()
        {
            string csv = GetCSVHeader();
            Func<TEntity, string> expr = x => this.Map == null ? this.FormatPropertiesCSV(x) : this.FormatMapCSV(x);
            csv += this.LineBreak + String.Join(this.LineBreak, this.DataSource.Select(expr));
            return csv;
        }

        private string FormatCSV(string str)
        {
            str = (str ?? "").Replace(this.Delimiter, "\"" + this.Delimiter + "\"");
            str = str.Replace(this.LineBreak, "\"" + this.LineBreak + "\"");
            str = str.Replace("\"", "\"\"");

            return String.Format("\"{0}\"", str);
        }

        private string FormatPropertiesCSV(TEntity obj)
        {
            string csv = "";

            foreach (var pi in this.SourceProperties)
            {
                string val = GetPropertyValue(pi, obj);
                csv += FormatCSV(val) + this.Delimiter;
            }

            csv = csv.TrimEnd(this.Delimiter.ToCharArray());
            return csv;
        }


        private string GetPropertyValue(PropertyInfo pi, object source)
        {
            try
            {
                var result = pi.GetValue(source, null);
                return (result == null) ? "" : result.ToString();
            }
            catch (Exception)
            {
                return "Can not obtain the value";
            }
        }

        private string FormatMapCSV(TEntity obj)
        {
            return String.Join(this.Delimiter, this.Map(obj).Select(x => FormatCSV(x)));
        }


        #endregion

    }
}

然后我定义导出操作方法如下:-

public ActionResult MyExportCSV()
        {
            IEnumerable<Employee> dataList = _dataSource.GetAll();
            return new CsvFileResult<Employee>(dataList, "toto.csv");
        }

现在上述方法在几乎 99% 的情况下都运行良好,但我面临的问题是,当数据包含嵌入的逗号时,我将获得由 "" 包围的 .csv 文件中的值。这是一个例子:-

  1. 我的数据库中有以下值test,123。现在,当我使用上述代码导出数据时,当我使用 MS excel 2010 打开 .csv 文件时,此值将显示为test","123。如图所示

  2. 当我使用 Notepad++ 打开 .csv 文件时,我会得到 "test"",""123"

所以我不确定问题出在哪里?它在我的代码里面吗?还是与 MS excel 2010 相关?我的意思是最后我期望值 test,123 在 MS excel 2010 中按原样显示,而不是 test","123

【问题讨论】:

  • 您不从事制作 CSV 库的业务。您正在为您的公司编写代码。不要浪费他们的时间重新发明轮子。使用能够处理 CSV 文件细微差别的现有良好支持的库。当您搜索“CSV”时,NuGet 上最受欢迎的库是 CsvHelper。我建议你从那里开始。
  • @mason 是的,我同意你的观点,没有必要重新发明轮子......现在例如,如果我想读取 csv 文件,我使用以下库 Microsoft.VisualBasic.FileIO,而不是重新发明轮子..但我找不到任何库来导出可以轻松使用 asp.net MVC 的数据。所以我提供的链接codeproject.com/Articles/1078092/… 似乎提供了一种简单的方法,可以导出数据,我可以重用相同的代码来导出我拥有的任何模型类......
  • @mason ... 现在关于您提供的 CsvHelper 库,是否有关于我如何在 asp.net mvc 中使用它的详细文档?第二个问题,你知道是什么导致了我提到的问题吗?我的意思是如果我能修复它,那么我认为我应该没问题......或者你建议尝试使用 CsvHelper 代替?
  • 当然我建议使用 CsvHelper。你重新发明了轮子。现在,您必须维护和修复某个具有 Code Project 帐户的随机人员创建的 CSV 编写库中的错误。你真的认为这是最好的利用你的时间吗?还是您认为最好利用您的时间来使用 CsvHelper,一个下载近 300 万次并被数千名开发人员使用的库,所有问题都已解决?至于让它在 ASP.NET MVC 中工作,它是一个创建 CSV 的 .NET 库。在 ASP.NET MVC 中使用它很简单。我链接到您的网站应该清楚说明
  • 尝试实现 CsvHelper。如果遇到困难,请提供MCVE。我会立即删除您从代码项目中复制粘贴的这段代码。这对您的应用程序来说是一个巨大的责任,而且是在浪费您的时间。

标签: c# asp.net asp.net-mvc excel csv


【解决方案1】:

你用双引号包围了分隔符,这不是你想要的。

private string FormatCSV(string str)
{
    str = (str ?? "").Replace(this.Delimiter, "\"" + this.Delimiter + "\"");
    str = str.Replace(this.LineBreak, "\"" + this.LineBreak + "\"");
    str = str.Replace("\"", "\"\"");

    return String.Format("\"{0}\"", str);
}

你只需要将你的字段内的双引号转义,然后将整个字段放在双引号中:

private string FormatCSV(string str)
{
    str = str.Replace("\"", "\"\"");
    return String.Format("\"{0}\"", str);
}

正确的数据应如下所示:"test,123",或者如果您的字段包含双引号,则如下所示:

"monitor 24"", Samsung"

【讨论】:

  • 所以你的意思是我不需要这两行代码:-str = (str ?? "").Replace(this.Delimiter, "\"" + this.Delimiter + "\""); str = str.Replace(this.LineBreak, "\"" + this.LineBreak + "\"");??现在基于维基百科en.wikipedia.org/wiki/Comma-separated_values上的csv文档,他们提到“必须引用带有嵌入换行符的字段”,这可以解释为什么codeproject.com/Articles/1078092/…的作者添加了这两行代码??
  • @johnG 是的,必须引用这些字段,但不能引用换行符或分隔符本身,这就是前两行中发生的情况。如果你删除这 2 行应该没问题(假设 FormatCSV() 只被调用字段值,而不是整行)。
猜你喜欢
  • 2012-03-22
  • 1970-01-01
  • 1970-01-01
  • 2020-07-20
  • 2021-01-24
  • 2018-01-10
  • 1970-01-01
  • 1970-01-01
  • 2023-01-16
相关资源
最近更新 更多