5
\$\begingroup\$

I have an IEnumerable of a view model that I need to convert to CSV and send back as a file response. I have written it out quite manually and I'm just seeking code review and suggestions on a more elegant solution, or alternatively if this looks acceptable, confirmation of that:

 public ActionResult ExportBasicReads(string nmi, DateTime fromDate, DateTime toDate)
 {
 var reads = _dataService.GetBasicReads(nmi, fromDate, toDate)
 .Where(r => r.CurrentReadQuality != ReadQuality.Estimate)
 .OrderBy(r => r.CurrentReadDate)
 .ThenBy(r => r.DeviceKey)
 .ThenBy(r => r.ValidationCode);
 StringBuilder csv = new StringBuilder();
 csv.AppendLine("Read Date,Device : Suffix,Read,Quality,Tran Code,Service Order,Previous Read Date,Previous Read,Previous Quality," +
 "Previous Tran Code,Dial Diff,Quantity,UOM,Dial Factor,Direction,Validation,From Date,To Date,Kwh,Avg Daily Load,Profile Name," +
 "Profle Area,Reading Timestamp,Update Date,ChangedBy,ChangeType,ChangeComment");
 reads.ForEach(x => csv.AppendLine(
 string.Format("{0},{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}",
 x.CurrentReadDate,
 x.DeviceKey,
 x.Suffix,
 x.CurrentReadRawValue,
 x.CurrentReadQualityDesc,
 x.CurrentReadTransactionCode,
 x.CurrentServiceOrder,
 x.PreviousReadDate,
 x.PreviousReadRawValue,
 x.PreviousReadQualityDesc,
 x.PreviousReadTransactionCode,
 x.DialDifference,
 x.Quantity,
 x.UOMDesc,
 x.Multiplier,
 x.DirectionDesc,
 x.ValidationDesc,
 x.EffectiveFromDateTime,
 x.EffectiveToDateTime,
 x.Kwh,
 x.AverageDailyLoad,
 x.ProfileName,
 x.ProfileArea,
 x.EffectiveFromDateTime,
 x.UpdateTimestamp,
 x.ChangedBy,
 x.ChangeType,
 x.ChangeComment
 )
 )); 
 return File(Encoding.ASCII.GetBytes(csv.ToString()), "text/csv", "BasicReadsExport.csv");
 }
Jesse C. Slicer
14.5k1 gold badge40 silver badges54 bronze badges
asked Jun 22, 2018 at 4:02
\$\endgroup\$
6
  • 2
    \$\begingroup\$ You can use string interpolation instead of string.Format()? \$\endgroup\$ Commented Jun 22, 2018 at 5:37
  • 2
    \$\begingroup\$ Looks pretty clean to me. \$\endgroup\$ Commented Jun 22, 2018 at 12:21
  • 1
    \$\begingroup\$ No need to reinvent - the popular ServiceStack.Text NuGet package has an extension method called .ToCsv(): docs.servicestack.net/csv-format \$\endgroup\$ Commented Jun 22, 2018 at 14:35
  • 1
    \$\begingroup\$ the string.Format is awfully hard to follow given the large number of parameters. I would suggest something like $"{x.CurrentReadDate},{x.DeviceKey}" .... This would also help avoid errors due to matching up parameter indexes if this code was later modified. \$\endgroup\$ Commented Jun 23, 2018 at 4:09
  • \$\begingroup\$ I'd create an extention method such as ToCvsLine(...) and use string interpolation inside that instead of string.Format. Then you just need to call it this way: reads.ForEach(_ => csv.AppendLine(_.ToCsvLine())); \$\endgroup\$ Commented Oct 25, 2019 at 15:29

1 Answer 1

4
\$\begingroup\$

I modified the formatting part for better readability and maintainability, at the cost of lower performance:

var columns = new Dictionary<string, Func<Reads, object>>
{
 ["Read Date"] = x => x.CurrentReadDate,
 ["Device"] = x => x.DeviceKey,
 ["Suffix"] = x => x.Suffix,
 ["Read"] = x => x.CurrentReadRawValue,
 // add the rest of headers/property selectors below
};
//var format = "{0},{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}";
var format = "{0}{1} : " + string.Join(",", columns.Select((_, i) => $"{{{i}}}").Skip(2));
var csv = new StringBuilder()
 .AppendFormat(format, columns.Keys.ToArray())
 .AppendLine();
foreach (var read in reads)
{
 var values = columns.Values.Select(selector => selector(read));
 csv
 .AppendFormat(format, values.ToArray())
 .AppendLine();
}

The advantage of this approach is that you can add/move/remove the columns without having to worry about updating the header and the format, and if they are kept in sync or not.

Note:

  • You can also revert to the constant format if it becomes more complex.
  • The property selector can be chained if you need special format on individual property, like: x => x.CurrentReadDate.ToString("yyyyMMddThhmm")
answered Jun 22, 2018 at 18:07
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.