كيف يمكنني إنشاء ملف CSV (ملف نصي محدد بفواصل) للتنزيل باستخدام ASP.NET بشكل أفضل؟

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

  •  09-06-2019
  •  | 
  •  

سؤال

هذا ما عندي.إنها تعمل.ولكن، هل هناك طريقة أبسط أو أفضل؟

في إحدى صفحات ASPX، لدي رابط التنزيل...

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

وبعد ذلك حصلت على رمز 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
هل كانت مفيدة؟

المحلول

يحتوي تنسيق CSV على بعض الأخطاء.هل سألت نفسك هذه الأسئلة:

  • هل تحتوي أي من بياناتي على فواصل مضمنة؟
  • هل تحتوي أي من بياناتي على علامتي اقتباس مزدوجتين؟
  • هل تحتوي أي من بياناتي على أسطر جديدة؟
  • هل أحتاج إلى دعم سلاسل Unicode؟

أرى العديد من المشاكل في التعليمات البرمجية الخاصة بك أعلاه.أول شيء الفاصلة..أنت تقوم بتجريد الفواصل:

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

لماذا؟في ملف CSV، يجب أن تحيط بأي شيء يحتوي على فواصل مع علامات اقتباس:

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

(أو شيء من هذا القبيل...VB الخاص بي ليس جيدًا جدًا).إذا لم تكن متأكدًا من وجود فواصل، لكن ضع علامات الاقتباس حولها.إذا كانت هناك علامات اقتباس في السلسلة، فأنت بحاجة إلى الهروب منها عن طريق مضاعفتها. " يصبح "".

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

ثم قم بإحاطة هذا بعلامات الاقتباس إذا كنت تريد ذلك.إذا كانت هناك أسطر جديدة في سلسلتك، فستحتاج إلى إحاطة السلسلة بعلامات اقتباس...نعم، خطوط جديدة حرفية نكون مسموح به في ملفات CSV.يبدو غريبا للبشر، ولكن كل شيء جيد.

يتطلب كاتب CSV الجيد بعض التفكير.يعد قارئ CSV (المحلل اللغوي) الجيد أمرًا صعبًا (ولا، التعبير العادي ليس جيدًا بما يكفي لتحليل ملف CSV...سوف يوصلك فقط حوالي 95% من الطريق إلى هناك).

ثم هناك يونيكود ...أو بشكل أعم قضايا I18N (التدويل).على سبيل المثال، تقوم بإزالة الفواصل من السعر المنسق.ولكن هذا بافتراض أن السعر تم تنسيقه كما تتوقعه في الولايات المتحدة.في فرنسا، يتم عكس تنسيق الأرقام (يتم استخدام النقاط بدلاً من الفواصل، و والعكس صحيح).خلاصة القول، استخدم التنسيق الحيادي للثقافة حيثما أمكن ذلك.

في حين أن القضية هنا توليد CSV، ستحتاج حتمًا إلى تحليل ملف CSV.في .NET، أفضل محلل وجدته (مجانًا) هو قارئ CSV سريع على CodeProject.لقد استخدمته بالفعل في كود الإنتاج وهو سريع حقًا وسهل الاستخدام للغاية!

نصائح أخرى

أقوم بتمرير جميع بيانات CSV الخاصة بي من خلال وظيفة مثل هذه:

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

وأيضًا، إذا كنت لا تقدم محتوى HTML، فمن المحتمل أنك تريد معالج http (.asحx) بدلاً من صفحة ويب كاملة.إذا قمت بإنشاء معالج جديد في Visual Studio، فمن المحتمل أنه يمكنك فقط نسخ التعليمات البرمجية الموجودة لديك إلى الطريقة الرئيسية وسوف تعمل، مع تعزيز أداء بسيط لجهودك.

يمكنك إنشاء ما يعادل bookString() في الاستعلام نفسه.إليك ما أعتقد أنه سيكون طريقة أبسط.

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.لست متأكدًا من الترخيص مفتوح المصدر الذي تخضع له، لكنه ساعدني كثيرًا.

هناك الكثير من الحمل المرتبط بفئة الصفحة.نظرًا لأنك تقوم للتو بإخراج ملف CSV ولا تحتاج إلى إعادة النشر أو عناصر تحكم الخادم أو التخزين المؤقت أو بقية الملف، فيجب عليك تحويله إلى معالج بامتداد .ashx. انظر هنا.

بالإضافة إلى ما قاله سيمون، قد ترغب في قراءة دليل كيفية استخدام ملف CSV وتأكد من عدم تشغيل مخرجاتك عبر أي من المشاكل.

ولتوضيح ما قاله سمعان:

ثم قم بإحاطة هذا بعلامات الاقتباس إذا كنت تريد ذلك

يجب أن تكون الحقول التي تحتوي على علامات اقتباس مزدوجة ("") محاطة بالكامل بعلامات اقتباس مزدوجة.لا ينبغي أن يكون هناك أي ضرر في مجرد تغليف جميع الحقول بعلامات اقتباس مزدوجة، إلا إذا كنت تريد على وجه التحديد أن يقوم المحلل اللغوي بإزالة المسافات البيضاء البادئة واللاحقة (بدلاً من قصها بنفسك).

أستخدم الطريقة التالية عند إنشاء ملف CSV من DataTable.ControllerContext هو مجرد كائن دفق الاستجابة حيث تتم كتابة الملف إليه.بالنسبة لك سيكون مجرد كائن الاستجابة.

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

تبدو جيدة في الغالب باستثناء وظيفتك "BookString()" حيث يجب عليك تمرير كل هذه السلاسل من خلال وظيفة صغيرة مثل هذه أولاً:

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 جيد لتلك السلاسل التي تعرف أنها خالية من الأسطر الجديدة (المدخلات من نماذج نصية بسيطة من سطر واحد، وما إلى ذلك).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top