I have a list of integer values (List) and would like to generate a string of comma delimited values. That is all items in the list output to a single comma delimted list.
My thoughts...
1. pass the list to a method.
2. Use stringbuilder to iterate the list and append commas
3. Test the last character and if it's a comma, delete it.
What are your thoughts? Is this the best way?
How would my code change if I wanted to handle not only integers (my current plan) but strings, longs, doubles, bools, etc, etc. in the future? I guess make it accept a list of any type.
It's amazing what the Framework already does for us.
List<int> myValues;
string csv = String.Join(",", myValues.Select(x => x.ToString()).ToArray());
For the general case:
IEnumerable<T> myList;
string csv = String.Join(",", myList.Select(x => x.ToString()).ToArray());
As you can see, it's effectively no different. Beware that you might need to actually wrap x.ToString() in quotes (i.e., "\"" + x.ToString() + "\"") in case x.ToString() contains commas.
For an interesting read on a slight variant of this: see Comma Quibbling on Eric Lippert's blog.
Note: This was written before .NET 4.0 was officially released. Now we can just say
IEnumerable<T> sequence;
string csv = String.Join(",", sequence);
using the overload String.Join<T>(string, IEnumerable<T>). This method will automatically project each element x to x.ToString().
in 3.5, i was still able to do this. Its much more simpler and doesnt need lambda.
String.Join(",", myList.ToArray<string>());
I explain it in-depth in this post. I'll just paste the code here with brief descriptions.
Here's the method that creates the header row. It uses the property names as column names.
private static void CreateHeader<T>(List<T> list, StreamWriter sw)
{
PropertyInfo[] properties = typeof(T).GetProperties();
for (int i = 0; i < properties.Length - 1; i++)
{
sw.Write(properties[i].Name + ",");
}
var lastProp = properties[properties.Length - 1].Name;
sw.Write(lastProp + sw.NewLine);
}
This method creates all the value rows
private static void CreateRows<T>(List<T> list, StreamWriter sw)
{
foreach (var item in list)
{
PropertyInfo[] properties = typeof(T).GetProperties();
for (int i = 0; i < properties.Length - 1; i++)
{
var prop = properties[i];
sw.Write(prop.GetValue(item) + ",");
}
var lastProp = properties[properties.Length - 1];
sw.Write(lastProp.GetValue(item) + sw.NewLine);
}
}
And here's the method that brings them together and creates the actual file.
public static void CreateCSV<T>(List<T> list, string filePath)
{
using (StreamWriter sw = new StreamWriter(filePath))
{
CreateHeader(list, sw);
CreateRows(list, sw);
}
}
You can create an extension method that you can call on any IEnumerable:
public static string JoinStrings<T>(
this IEnumerable<T> values, string separator)
{
var stringValues = values.Select(item =>
(item == null ? string.Empty : item.ToString()));
return string.Join(separator, stringValues.ToArray());
}
Then you can just call the method on the original list:
string commaSeparated = myList.JoinStrings(", ");
If any body wants to convert list of custom class objects instead of list of string then override the ToString method of your class with csv row representation of your class.
Public Class MyClass{
public int Id{get;set;}
public String PropertyA{get;set;}
public override string ToString()
{
return this.Id+ "," + this.PropertyA;
}
}
Then following code can be used to convert this class list to CSV with header column
string csvHeaderRow = String.Join(",", typeof(MyClass).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name).ToArray<string>()) + Environment.NewLine;
string csv= csvHeaderRow + String.Join(Environment.NewLine, MyClass.Select(x => x.ToString()).ToArray());
You can use String.Join.
String.Join(
",",
Array.ConvertAll(
list.ToArray(),
element => element.ToString()
)
);
As the code in the link given by #Frank Create a CSV File from a .NET Generic List there was a little issue of ending every line with a , I modified the code to get rid of it.Hope it helps someone.
/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
if (list == null || list.Count == 0) return;
//get type from 0th member
Type t = list[0].GetType();
string newLine = Environment.NewLine;
if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));
if (!File.Exists(csvCompletePath)) File.Create(csvCompletePath);
using (var sw = new StreamWriter(csvCompletePath))
{
//make a new instance of the class name we figured out to get its props
object o = Activator.CreateInstance(t);
//gets all properties
PropertyInfo[] props = o.GetType().GetProperties();
//foreach of the properties in class above, write out properties
//this is the header row
sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);
//this acts as datarow
foreach (T item in list)
{
//this acts as datacolumn
var row = string.Join(",", props.Select(d => item.GetType()
.GetProperty(d.Name)
.GetValue(item, null)
.ToString())
.ToArray());
sw.Write(row + newLine);
}
}
}
I like a nice simple extension method
public static string ToCsv(this List<string> itemList)
{
return string.Join(",", itemList);
}
Then you can just call the method on the original list:
string CsvString = myList.ToCsv();
Cleaner and easier to read than some of the other suggestions.
For whatever reason, #AliUmair reverted the edit to his answer that fixes his code that doesn't run as is, so here is the working version that doesn't have the file access error and properly handles null object property values:
/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
if (list == null || list.Count == 0) return;
//get type from 0th member
Type t = list[0].GetType();
string newLine = Environment.NewLine;
if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));
using (var sw = new StreamWriter(csvCompletePath))
{
//make a new instance of the class name we figured out to get its props
object o = Activator.CreateInstance(t);
//gets all properties
PropertyInfo[] props = o.GetType().GetProperties();
//foreach of the properties in class above, write out properties
//this is the header row
sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);
//this acts as datarow
foreach (T item in list)
{
//this acts as datacolumn
var row = string.Join(",", props.Select(d => $"\"{item.GetType().GetProperty(d.Name).GetValue(item, null)?.ToString()}\"")
.ToArray());
sw.Write(row + newLine);
}
}
}
Any solution work only if List a list(of string)
If you have a generic list of your own Objects like list(of car) where car has n properties, you must loop the PropertiesInfo of each car object.
Look at: http://www.csharptocsharp.com/generate-csv-from-generic-list
CsvHelper library is very popular in the Nuget.You worth it,man!
https://github.com/JoshClose/CsvHelper/wiki/Basics
Using CsvHelper is really easy. It's default settings are setup for the most common scenarios.
Here is a little setup data.
Actors.csv:
Id,FirstName,LastName
1,Arnold,Schwarzenegger
2,Matt,Damon
3,Christian,Bale
Actor.cs (custom class object that represents an actor):
public class Actor
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Reading the CSV file using CsvReader:
var csv = new CsvReader( new StreamReader( "Actors.csv" ) );
var actorsList = csv.GetRecords();
Writing to a CSV file.
using (var csv = new CsvWriter( new StreamWriter( "Actors.csv" ) ))
{
csv.WriteRecords( actorsList );
}
The problem with String.Join is that you are not handling the case of a comma already existing in the value. When a comma exists then you surround the value in Quotes and replace all existing Quotes with double Quotes.
String.Join(",",{"this value has a , in it","This one doesn't", "This one , does"});
See CSV Module
http://cc.davelozinski.com/c-sharp/the-fastest-way-to-read-and-process-text-files
This website did some extensive testing about how to write to a file using buffered writer, reading line by line seems to be the best way, using string builder was one of the slowest.
I use his techniques a great deal for writing stuff to file it works well.
A general purpose ToCsv() extension method:
Supports Int16/32/64, float, double, decimal, and anything supporting
ToString()
Optional custom join separator
Optional custom selector
Optional null/empty handling specification (*Opt() overloads)
Usage Examples:
"123".ToCsv() // "1,2,3"
"123".ToCsv(", ") // "1, 2, 3"
new List<int> { 1, 2, 3 }.ToCsv() // "1,2,3"
new List<Tuple<int, string>>
{
Tuple.Create(1, "One"),
Tuple.Create(2, "Two")
}
.ToCsv(t => t.Item2); // "One,Two"
((string)null).ToCsv() // throws exception
((string)null).ToCsvOpt() // ""
((string)null).ToCsvOpt(ReturnNullCsv.WhenNull) // null
Implementation
/// <summary>
/// Specifies when ToCsv() should return null. Refer to ToCsv() for IEnumerable[T]
/// </summary>
public enum ReturnNullCsv
{
/// <summary>
/// Return String.Empty when the input list is null or empty.
/// </summary>
Never,
/// <summary>
/// Return null only if input list is null. Return String.Empty if list is empty.
/// </summary>
WhenNull,
/// <summary>
/// Return null when the input list is null or empty
/// </summary>
WhenNullOrEmpty,
/// <summary>
/// Throw if the argument is null
/// </summary>
ThrowIfNull
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
this IEnumerable<T> values,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, null /*selector*/, ReturnNullCsv.ThrowIfNull, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
this IEnumerable<T> values,
Func<T, string> selector,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, selector, ReturnNullCsv.ThrowIfNull, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
this IEnumerable<T> values,
ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, null /*selector*/, returnNullCsv, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
this IEnumerable<T> values,
Func<T, string> selector,
ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
string joinSeparator = ",")
{
switch (returnNullCsv)
{
case ReturnNullCsv.Never:
if (!values.AnyOpt())
return string.Empty;
break;
case ReturnNullCsv.WhenNull:
if (values == null)
return null;
break;
case ReturnNullCsv.WhenNullOrEmpty:
if (!values.AnyOpt())
return null;
break;
case ReturnNullCsv.ThrowIfNull:
if (values == null)
throw new ArgumentOutOfRangeException("ToCsvOpt was passed a null value with ReturnNullCsv = ThrowIfNull.");
break;
default:
throw new ArgumentOutOfRangeException("returnNullCsv", returnNullCsv, "Out of range.");
}
if (selector == null)
{
if (typeof(T) == typeof(Int16) ||
typeof(T) == typeof(Int32) ||
typeof(T) == typeof(Int64))
{
selector = (v) => Convert.ToInt64(v).ToStringInvariant();
}
else if (typeof(T) == typeof(decimal))
{
selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
}
else if (typeof(T) == typeof(float) ||
typeof(T) == typeof(double))
{
selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
}
else
{
selector = (v) => v.ToString();
}
}
return String.Join(joinSeparator, values.Select(v => selector(v)));
}
public static string ToStringInvariantOpt(this Decimal? d)
{
return d.HasValue ? d.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Decimal d)
{
return d.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int64? l)
{
return l.HasValue ? l.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int64 l)
{
return l.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int32? i)
{
return i.HasValue ? i.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int32 i)
{
return i.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int16? i)
{
return i.HasValue ? i.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int16 i)
{
return i.ToString(CultureInfo.InvariantCulture);
}
Here is my extension method, it returns a string for simplicity but my implementation writes the file to a data lake.
It provides for any delimiter, adds quotes to string (in case they contain the delimiter) and deals will nulls and blanks.
/// <summary>
/// A class to hold extension methods for C# Lists
/// </summary>
public static class ListExtensions
{
/// <summary>
/// Convert a list of Type T to a CSV
/// </summary>
/// <typeparam name="T">The type of the object held in the list</typeparam>
/// <param name="items">The list of items to process</param>
/// <param name="delimiter">Specify the delimiter, default is ,</param>
/// <returns></returns>
public static string ToCsv<T>(this List<T> items, string delimiter = ",")
{
Type itemType = typeof(T);
var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
var csv = new StringBuilder();
// Write Headers
csv.AppendLine(string.Join(delimiter, props.Select(p => p.Name)));
// Write Rows
foreach (var item in items)
{
// Write Fields
csv.AppendLine(string.Join(delimiter, props.Select(p => GetCsvFieldasedOnValue(p, item))));
}
return csv.ToString();
}
/// <summary>
/// Provide generic and specific handling of fields
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p"></param>
/// <param name="item"></param>
/// <returns></returns>
private static object GetCsvFieldasedOnValue<T>(PropertyInfo p, T item)
{
string value = "";
try
{
value = p.GetValue(item, null)?.ToString();
if (value == null) return "NULL"; // Deal with nulls
if (value.Trim().Length == 0) return ""; // Deal with spaces and blanks
// Guard strings with "s, they may contain the delimiter!
if (p.PropertyType == typeof(string))
{
value = string.Format("\"{0}\"", value);
}
}
catch (Exception ex)
{
throw ex;
}
return value;
}
}
Usage:
// Tab Delimited (TSV)
var csv = MyList.ToCsv<MyClass>("\t");
The other answers work, but my issue is loading unknown data from the database, so I needed something a bit more robust than what's already here.
I wanted something that fit the following requirements:
able to be opened in excel
had to be able to handle date time formats in an excel compatible way
had to automatically exclude linked entities (EF navigation properties)
had to support column contents containing " and the delimiter ,
had to support nullable columns
had to support a wide array of data types
numbers of every kind
guids
datetimes
custom type definitions (ie name from a linked entity)
I used month/day/year formats for the date exports for compatibility reasons
public static IReadOnlyDictionary<System.Type, Func<object, string>> CsvTypeFormats = new Dictionary<System.Type, Func<object, string>> {
// handles escaping column delimiter (',') and quote marks
{ typeof(string), x => string.IsNullOrWhiteSpace(x as string) ? null as string : $"\"{(x as string).Replace("\"", "\"\"")}\""},
{ typeof(DateTime), x => $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTime?), x => x == null ? "" : $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTimeOffset), x => $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTimeOffset?), x => x == null ? "" : $"{x:M/d/yyyy H:m:s.fff}" },
};
public void WriteCsvContent<T>(ICollection<T> data, StringBuilder writer, IDictionary<System.Type, Func<object, string>> explicitMapping = null)
{
var typeMappings = CsvTypeFormats.ToDictionary(x=>x.Key, x=>x.Value);
if (explicitMapping != null) {
foreach(var mapping in explicitMapping) {
typeMappings[mapping.Key] = mapping.Value;
}
}
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => IsSimpleType(x.PropertyType))
.ToList();
// header row
writer.AppendJoin(',', props.Select(x => x.Name));
writer.AppendLine();
foreach (var item in data)
{
writer.AppendJoin(',',
props.Select(prop => typeMappings.ContainsKey(prop.PropertyType)
? typeMappings[prop.PropertyType](prop.GetValue(item))
: prop.GetValue(item)?.ToString() ?? ""
)
// escaping and special characters
.Select(x => x != null && x != "" ? $"\"{x.Replace("\"", "\"\"")}\"" : null)
);
writer.AppendLine();
}
}
private bool IsSimpleType(System.Type t)
{
return
t.IsPrimitive ||
t.IsValueType ||
t.IsEnum ||
(t == typeof(string)) ||
CsvTypeFormats.ContainsKey(t);
}
If your class uses fields instead of properties, change the GetProperties to GetFields and the PropertyType accessors to FieldType
Related
I have list of Objects upto 150000 and I want to bind to razor view at run time for it export as EXCEL but at the time binding i am getting out of memory exception , is there any workaround to overcome this limitation ?
Export Method :
public void ExportToExcel()
{
string viewPath = "~/Modules/Reports/Views/" + TempData["reportName"] + ".cshtml";
string viewPathCopy = "~/Modules/Reports/Views/" + TempData["reportName"] + "2.cshtml";
string serverViewPath = Server.MapPath(viewPath);
string serverViewPathCopy = Server.MapPath(viewPathCopy);
if (System.IO.File.Exists(serverViewPathCopy))
{
System.IO.File.Delete(serverViewPathCopy);
}
System.IO.File.Copy(serverViewPath, serverViewPathCopy);
string viewContents = System.IO.File.ReadAllText(serverViewPathCopy).Replace("thead", "tr");
viewContents += "<style>body{font-size:8px !important;}table {padding:0 !important,margin:0 !important}</style>";
System.IO.File.WriteAllText(serverViewPathCopy, viewContents);
System.IO.File.WriteAllText(viewPathCopy, TemplateFromFile(viewPathCopy, TempData["reportData"]));
FileInfo file = new FileInfo(viewPathCopy);
if (file.Exists)
{
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + file.Name + ".xls");
Response.AddHeader("Content-Length", file.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.WriteFile(file.FullName);
Response.End();
}
else
{
Response.Write("This file does not exist.");
}
}
Binding Model To View :
public string TemplateFromFile(string file, dynamic model)
{
string template = "";
TextReader textReader = new StreamReader(HelperMethods.GetFullFilePath(file));
try
{
template = textReader.ReadToEnd();
}
finally
{
textReader.Close();
}
return Razor.Parse(template, model);
}
If i have large amounts of data to export, my goto tool is DoddleReport this lets you create a valid xlsx export. With doddle you can even create multi tab excel sheets (see my example on Github).
Some code
Extension for IEnumerable
public static ExportBuilder<TModel> Export<TModel>(this IEnumerable<TModel> models) where TModel : class
{
return ExportBuilder<TModel>.Create(models);
}
The Export Builder
public class ExportBuilder<TModel> where TModel : class
{
private readonly IEnumerable<TModel> _models;
private readonly ICollection<IExportColumn<TModel>> _columns;
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
private ExportBuilder(IEnumerable<TModel> models)
{
_models = models;
_columns = new List<IExportColumn<TModel>>();
}
public static ExportBuilder<TModel> Create(IEnumerable<TModel> models)
{
return new ExportBuilder<TModel>(models);
}
public ExportBuilder<TModel> Column<TProperty>(Expression<Func<TModel, TProperty>> display)
{
if (!(display.Body is MemberExpression))
throw new ArgumentException(display + " is not a property!");
var memberInfo = ((MemberExpression)display.Body).Member;
if (!memberInfo.HasAttribute<DisplayNameAttribute>())
throw new ArgumentException(display + " does not have a [Display] attribute");
var displayAttribute = ExtensionsForMemberInfo.GetAttribute<DisplayNameAttribute>(memberInfo);
_columns.Add(new ExportColumn<TModel, TProperty>(displayAttribute.DisplayName, display));
return this;
}
public ExportBuilder<TModel> Column<TProperty>(string header, Expression<Func<TModel, TProperty>> property)
{
_columns.Add(new ExportColumn<TModel, TProperty>(header, property));
return this;
}
public IReportSource ToReportSource()
{
if (_models.Any())
{
return DoddleExporter.ToReportSource(_models.Select(model => _columns.ToDictionary(c => c.Header, c => c.Display(model))));
}
var result = _columns
.ToDictionary(a => a.Header, a => string.Empty);
return DoddleExporter.ToReportSource(new[] { result });
}
public Report ToReport([CanBeNull] IEnumerable<KeyValuePair<string, string>> headers, [CanBeNull] IReportWriter writer = null)
{
headers = headers ?? Enumerable.Empty<KeyValuePair<string, string>>();
var report = new Report(ToReportSource(), writer);
//report.TextFields.Footer = string.Format(#"Aangemaakt op: {0}", DateTime.Now.ToString(DataFormatStrings.Date));
var headersArray = headers as KeyValuePair<string, string>[] ?? headers.ToArray();
if (headersArray.Any())
{
report.TextFields.Header = headersArray.Aggregate(string.Empty,
(currentHeaders, header) => string.Format("{0}{3}{1} : {2}", currentHeaders, header.Key, header.Value, Environment.NewLine));
}
return report;
}
public ReportResult ToExcelReportResult([CanBeNull] IEnumerable<KeyValuePair<string, string>> headers)
{
return new ReportResult(ToReport(headers), new ExcelReportWriter(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
}
The Exporter
public static class DoddleExporter
{
/// <summary>
/// Converts an enumerable of dictionaries to a report source
/// </summary>
/// <typeparam name="TValue">
/// The type of values in the dictionaries
/// </typeparam>
/// <param name="elements">
/// An enumerable of elements
/// </param>
/// <returns>The report source that was created from the elements</returns>
public static IReportSource ToReportSource<TValue>(IEnumerable<IDictionary<string, TValue>> elements)
{
var elementsArray = elements.ToArray();
if (!elementsArray.Any())
throw new ArgumentException("Can't export empty list of elements");
return ToReportSource(elementsArray, elementsArray.First().Keys.ToArray(),
(element, key) => element.ContainsKey(key) ? element[key] : default(TValue));
}
/// <summary>
/// Converts an enumerable of XElement to a report source
/// </summary>
/// <param name="rootElements">
/// The xml root elements that contain the values
/// </param>
/// <param name="keys">
/// They keys that can be used to fetch values from each root element
/// </param>
/// <returns>The report source that was created from the elements</returns>
public static IReportSource ToReportSource(IEnumerable<XElement> rootElements, string[] keys)
{
return ToReportSource(rootElements, keys, delegate(XElement element, string key)
{
var value = element.Element(XmlConvert.EncodeLocalName(key));
return value != null ? value.Value : null;
});
}
/// <summary>
/// Converts a list of elements to a report source
/// </summary>
/// <param name="elements">
/// An enumerable of elements
/// </param>
/// <param name="keys">
/// They keys with which the values can be fetched from one element
/// </param>
/// <param name="valueSelector">
/// The function with which one value can be fetched given one key and one element
/// </param>
/// <returns>The report source that was created from the elements</returns>
public static IReportSource ToReportSource<T>(IEnumerable<T> elements, string[] keys,
Func<T, string, object> valueSelector)
{
var expandos = new List<ExpandoObject>();
foreach (var element in elements)
{
var expando = new ExpandoObject();
var expandoDictionary = (IDictionary<string, object>) expando;
foreach (var key in keys)
{
var value = valueSelector(element, key);
expandoDictionary[key] = value;
}
expandos.Add(expando);
}
return expandos.ToReportSource();
}
}
Helper classes
public interface IExportColumn<TModel> where TModel : class
{
string Header { get; }
Func<TModel, Object> Display { get; }
}
public class ExportColumn<TModel, TProperty> : IExportColumn<TModel> where TModel : class
{
private readonly string _header;
private readonly Expression<Func<TModel, TProperty>> _display;
public string Header { get { return _header; } }
public Func<TModel, Object> Display { get { return model => _display.Compile().Invoke(model); } }
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public ExportColumn(string header, Expression<Func<TModel, TProperty>> display)
{
_header = header;
_display = display; ;
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
return string.Format("Header: {0}, Display: {1}", _header, _display);
}
}
Usage
var report = probing.Measurements.Export()
.Column(MeasurementResource.Depth, m => m.Depth)
.Column(MeasurementResource.DepthBelowWater, m => m.DepthBelowWater)
.Column(MeasurementResource.ResistancePoint, m => m.ResistancePoint)
.Column(MeasurementResource.FrictionLateral, m => m.FrictionLateral)
.Column(MeasurementResource.FrictionLocal, m => m.FrictionLocal)
.Column(MeasurementResource.FrictionTotal, m => m.FrictionTotal)
.Column(MeasurementResource.Inclination, m => m.Inclination)
.Column(MeasurementResource.PoreWaterPressure, m => m.PoreWaterPressure)
.Column(MeasurementResource.Speed, m => m.Speed)
.Column(MeasurementResource.CalcAlpha, m => m.CalcAlpha)
.Column(MeasurementResource.CalcGammaDry, m => m.CalcGammaDry)
.Column(MeasurementResource.CalcGammaWet, m => m.CalcGammaWet)
.Column(MeasurementResource.GrainTension, m => m.GrainTension)
.Column(MeasurementResource.CompressionCoefficient, m => m.CompressionCoefficient)
.ToReport(null, new ExcelReportWriter());
var stream = new MemoryStream();
writer.WriteReport(report, stream);
stream.Seek(0, SeekOrigin.Begin);
return File(stream, "application/vnd.ms-excel", string.Format(#"{0}-{1}.xlsx", probing.Project.ProjectNumber, probing.ProbingNumber));
The database I am working with currently has a varchar field, and in my code I want to map the potential values to a enumeration like:
public enum UserStatus
{
Anonymous,
Enrolled,
SuperUser
}
At the database level for this column, there is a constrain on it where the value has to be:
ANONYMOUS
ENROLLED
SUPERUSER
Is it possible for me to do:
UserStatus.SuperUser.ToString()
And have that value be SUPERUSER, and this be consistant and not screw up down the road?
A better solution may be to take advantage of the DescriptionAttribute:
public enum UserStatus
{
[Description("ANONYMOUS")]
Anonymous,
[Description("ENROLLED")]
Enrolled,
[Description("SUPERUSER")]
SuperUser
}
Then use something like:
/// <summary>
/// Class EnumExtenions
/// </summary>
public static class EnumExtenions
{
/// <summary>
/// Gets the description.
/// </summary>
/// <param name="e">The e.</param>
/// <returns>String.</returns>
public static String GetDescription(this Enum e)
{
String enumAsString = e.ToString();
Type type = e.GetType();
MemberInfo[] members = type.GetMember(enumAsString);
if (members != null && members.Length > 0)
{
Object[] attributes = members[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
enumAsString = ((DescriptionAttribute)attributes[0]).Description;
}
}
return enumAsString;
}
/// <summary>
/// Gets an enum from its description.
/// </summary>
/// <typeparam name="TEnum">The type of the T enum.</typeparam>
/// <param name="description">The description.</param>
/// <returns>Matching enum value.</returns>
/// <exception cref="System.InvalidOperationException"></exception>
public static TEnum GetFromDescription<TEnum>(String description)
where TEnum : struct, IConvertible // http://stackoverflow.com/a/79903/298053
{
if (!typeof(TEnum).IsEnum)
{
throw new InvalidOperationException();
}
foreach (FieldInfo field in typeof(TEnum).GetFields())
{
DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null)
{
if (attribute.Description == description)
{
return (TEnum)field.GetValue(null);
}
}
else
{
if (field.Name == description)
{
return (TEnum)field.GetValue(null);
}
}
}
return default(TEnum);
}
}
So now you're referencing UserStatus.Anonymous.GetDescription().
Of course you could always make your own DatabaseMapAttribute (or what-have-you) and create your own extension methods. Then you can kill a reference to System.ComponentModel. Completely your call.
You can't override ToString for enums, instead you can create your own Extension Method like:
public static class MyExtensions
{
public static string ToUpperString(this UserStatus userStatus)
{
return userStatus.ToString().ToUpper();// OR .ToUpperInvariant
}
}
And then call it like:
string str = UserStatus.Anonymous.ToUpperString();
Enum.ToString supports 4 different formats. I'd go for:
UserStatus.SuperUser.ToString("G").ToUpper();
"G" ensures that it will try first to get the string representation of your enum.
This is my method:
/// <summary>
/// Uses Dictionary(Key,Value) where key is the property and value is the field name.
/// Matches the dictionary of mandatory fields with object properties
/// and checks whether the current object has values in it or
/// not.
/// </summary>
/// <param name="mandatoryFields">List of string - properties</param>
/// <param name="o">object of the current class</
/// <param name="message">holds the message for end user to display</param>
/// <returns>The name of the property</returns>
public static bool CheckMandatoryFields(Dictionary<string,string > mandatoryFields, object o,out StringBuilder message)
{
message = new StringBuilder();
if(mandatoryFields !=null && mandatoryFields.Count>0)
{
var sourceType = o.GetType();
var properties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Static);
for (var i = 0; i < properties.Length; i++)
{
if (mandatoryFields.Keys.Contains(properties[i].Name))
{
if (string.IsNullOrEmpty( properties[i].GetValue(o, null).ToString()))
{
message.AppendLine(string.Format("{0} name is blank.", mandatoryFields.Values));
}
}
}
if(message.ToString().Trim().Length>0)
{
return false;
}
}
return true;
}
In this I have params Dictionary which will hold the property name of the class and its corresponding fieldname from the UI(manually fed by developer in the business layer or UI).
So what I want is that when the property is on the way to validate, if the property is found null or blank, then its corresponding fieldname, which is actually the value of the dictionary will get added to the stringbuilder message in the method above.
I hope i am clear.
Do the loop the other way:
public static bool CheckMandatoryFields(Dictionary<string,string > mandatoryFields, object o,out StringBuilder message)
{
message = new StringBuilder();
if(mandatoryFields == null || mandatoryFields.Count == 0)
{
return true;
}
var sourceType = o.GetType();
foreach (var mandatoryField in mandatoryFields) {
var property = sourceType.GetProperty(mandatoryField.Key, BindingFlags.Public | BindingFlags.Static);
if (property == null) {
continue;
}
if (string.IsNullOrEmpty(property.GetValue(o, null).ToString()))
{
message.AppendLine(string.Format("{0} name is blank.", mandatoryField.Value));
}
}
return message.ToString().Trim().Length == 0;
}
This way you iterate over the properties you want to check, so you always have a handle on the "current" property and know the corresponding key and value from the dictionary.
The snippet
if (property == null) {
continue;
}
causes the function to treat properties that exist as names in the dictionary but not as actual properties on the type to be treated as valid, to mirror what your original code does.
I'm writing some Enum functionality, and have the following:
public static T ConvertStringToEnumValue<T>(string valueToConvert,
bool isCaseSensitive)
{
if (String.IsNullOrWhiteSpace(valueToConvert))
return (T)typeof(T).TypeInitializer.Invoke(null);
valueToConvert = valueToConvert.Replace(" ", "");
if (typeof(T).BaseType.FullName != "System.Enum" &&
typeof(T).BaseType.FullName != "System.ValueType")
{
throw new ArgumentException("Type must be of Enum and not " +
typeof (T).BaseType.FullName);
}
if (typeof(T).BaseType.FullName == "System.ValueType")
{
return (T)Enum.Parse(Nullable.GetUnderlyingType(typeof(T)),
valueToConvert, !isCaseSensitive);
}
return (T)Enum.Parse(typeof(T), valueToConvert, !isCaseSensitive);
}
I now call this with the following:
EnumHelper.ConvertStringToEnumValue<Enums.Animals?>("Cat");
This works as expected. However, if I run this:
EnumHelper.ConvertStringToEnumValue<Enums.Animals?>(null);
it breaks with the error that the TypeInitializer is null.
Does anyone know how to solve this?
Thanks all!
try
if (String.IsNullOrWhiteSpace(valueToConvert))
return default(T);
I have an different approach, using extension and generics.
public static T ToEnum<T>(this string s) {
if (string.IsNullOrWhiteSpace(s))
return default(T);
s = s.Replace(" ", "");
if (typeof(T).BaseType.FullName != "System.Enum" &&
typeof(T).BaseType.FullName != "System.ValueType") {
throw new ArgumentException("Type must be of Enum and not " + typeof(T).BaseType.FullName);
}
if (typeof(T).BaseType.FullName == "System.ValueType")
return (T)Enum.Parse(Nullable.GetUnderlyingType(typeof(T)), s, true);
return (T)Enum.Parse(typeof(T), s, true);
}
Use like this...
Gender? g = "Female".ToEnum<Gender?>();
This one gets the job done and it also looks pretty. Hope it helps!
/// <summary>
/// <para>More convenient than using T.TryParse(string, out T).
/// Works with primitive types, structs, and enums.
/// Tries to parse the string to an instance of the type specified.
/// If the input cannot be parsed, null will be returned.
/// </para>
/// <para>
/// If the value of the caller is null, null will be returned.
/// So if you have "string s = null;" and then you try "s.ToNullable...",
/// null will be returned. No null exception will be thrown.
/// </para>
/// <author>Contributed by Taylor Love (Pangamma)</author>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_self"></param>
/// <returns></returns>
public static T? ToNullable<T>(this string p_self) where T : struct
{
if (!string.IsNullOrEmpty(p_self))
{
var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(T));
if (converter.IsValid(p_self)) return (T)converter.ConvertFromString(p_self);
if (typeof(T).IsEnum) { T t; if (Enum.TryParse<T>(p_self, out t)) return t;}
}
return null;
}
https://github.com/Pangamma/PangammaUtilities-CSharp/tree/master/src/StringExtensions
this is what I using, maybe useful for someone!
public static class EnumExtension
{
public static TEnum? ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum? #default = default)
where TEnum : struct, Enum
{
TEnum? #enum;
try { #enum = (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase); }
catch { #enum = #default; }
return #enum;
}
}
I got an Int16 value, from the database, and need to convert this to an enum type. This is unfortunately done in a layer of the code that knows very little about the objects except for what it can gather through reflection.
As such, it ends up calling Convert.ChangeType which fails with an invalid cast exception.
I found what I consider a smelly workaround, like this:
String name = Enum.GetName(destinationType, value);
Object enumValue = Enum.Parse(destinationType, name, false);
Is there a better way, so that I don't have to move through this String operation?
Here's a short, but complete, program that can be used if anyone need to experiment:
using System;
public class MyClass
{
public enum DummyEnum
{
Value0,
Value1
}
public static void Main()
{
Int16 value = 1;
Type destinationType = typeof(DummyEnum);
String name = Enum.GetName(destinationType, value);
Object enumValue = Enum.Parse(destinationType, name, false);
Console.WriteLine("" + value + " = " + enumValue);
}
}
Enum.ToObject(.... is what you're looking for!
C#
StringComparison enumValue = (StringComparison)Enum.ToObject(typeof(StringComparison), 5);
VB.NET
Dim enumValue As StringComparison = CType([Enum].ToObject(GetType(StringComparison), 5), StringComparison)
If you do a lot of Enum converting try using the following class it will save you alot of code.
public class Enum<EnumType> where EnumType : struct, IConvertible
{
/// <summary>
/// Retrieves an array of the values of the constants in a specified enumeration.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType[] GetValues()
{
return (EnumType[])Enum.GetValues(typeof(EnumType));
}
/// <summary>
/// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType Parse(string name)
{
return (EnumType)Enum.Parse(typeof(EnumType), name);
}
/// <summary>
/// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.
/// </summary>
/// <param name="name"></param>
/// <param name="ignoreCase"></param>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType Parse(string name, bool ignoreCase)
{
return (EnumType)Enum.Parse(typeof(EnumType), name, ignoreCase);
}
/// <summary>
/// Converts the specified object with an integer value to an enumeration member.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <remarks></remarks>
public static EnumType ToObject(object value)
{
return (EnumType)Enum.ToObject(typeof(EnumType), value);
}
}
Now instead of writing (StringComparison)Enum.ToObject(typeof(StringComparison), 5); you can simply write Enum<StringComparison>.ToObject(5);.
Based on the #Peter's answer here is the method for Nullable<int> to Enum conversion:
public static class EnumUtils
{
public static bool TryParse<TEnum>(int? value, out TEnum result)
where TEnum: struct, IConvertible
{
if(!value.HasValue || !Enum.IsDefined(typeof(TEnum), value)){
result = default(TEnum);
return false;
}
result = (TEnum)Enum.ToObject(typeof(TEnum), value);
return true;
}
}
Using EnumUtils.TryParse<YourEnumType>(someNumber, out result) becomes useful for many scenarios. For example, WebApi Controller in Asp.NET does not have default protection against invalid Enum params. Asp.NET will just use default(YourEnumType) value, even if some passes null, -1000, 500000, "garbage string" or totally ignores the parameter. Moreover, ModelState will be valid in all these cases, so one of the solution is to use int? type with custom check
public class MyApiController: Controller
{
[HttpGet]
public IActionResult Get(int? myEnumParam){
MyEnumType myEnumParamParsed;
if(!EnumUtils.TryParse<MyEnumType>(myEnumParam, out myEnumParamParsed)){
return BadRequest($"Error: parameter '{nameof(myEnumParam)}' is not specified or incorrect");
}
return this.Get(washingServiceTypeParsed);
}
private IActionResult Get(MyEnumType myEnumParam){
// here we can guarantee that myEnumParam is valid
}
If you are storing an Enum in a DataTable but don't know which column is an enum and which is a string/int, you can access the value this way:
foreach (DataRow dataRow in myDataTable.Rows)
{
Trace.WriteLine("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
foreach (DataColumn dataCol in myDataTable.Columns)
{
object v = dataRow[dataCol];
Type t = dataCol.DataType;
bool e = false;
if (t.IsEnum) e = true;
Trace.WriteLine((dataCol.ColumnName + ":").PadRight(30) +
(e ? Enum.ToObject(t, v) : v));
}
}