Export Razor View To Excel With Big DataSet - c#

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

Related

InvalidCastException JToken to <Class> JSON.net

TL;DR: I was trying to create a class which would hold nested JSON data.
I eventually solved my own problem, but #dbc was very helpful and they have a solution which may be slightly faster if you want to implement it their way. I have fully documented my solution, with example usage, and marked it as answered below.
I'm creating a project in which I intend to store lots of nested JSON data.
Instead of creating a hundred classes, each with their own variables/attributes, and then having to modify them every time I want to change something, I'd like to create a simple "dynamic object".
This object holds the root of all data, as well as all the children's data. In JSON, this is represented by:
{
"name":"foo",
"id":0,
"attributes":
{
"slippery":true,
"dangerous":true
},
"costs":
{
"move":1,
"place":2,
"destroy":3
}
}
where the root structure holds the data "name" and "id", as well as children "attributes" and "costs" each containing their own data.
I'm using the json.net library for this, and my current class looks like this:
public class Data : JObject
{
public void CreateChildUnderParent(string parent, string child)
{
Data obj = GetValueOfKey<Data>(parent);
if(obj != null)
obj.CreateChild(child);
}
public void CreateChild(string child)
{
AddKey(child, new Data());
}
public void AddKeyToParent(string parent, string key, JToken value)
{
Data parentObject = GetValueOfKey<Data>(parent);
if(parentObject != null)
parentObject.AddKey(key, value);
}
public void AddKey(string key, JToken value)
{
Add(key, value);
}
public void RemoveKeyFromParent(string parent, string key)
{
Data parentObject = GetValueOfKey<Data>(parent);
if(parentObject != null)
parentObject.RemoveKey(key);
}
public void RemoveKey(string key)
{
Remove(key);
}
public T GetValueFromParent<T>(string parent, string key)
{
Data parentObject = GetValueOfKey<Data>(parent);
if(parentObject != null)
return parentObject.GetValue(key).ToObject<T>();
return default;
}
public T GetValueOfKey<T>(string key)
{
foreach (var kvp in this)
if (kvp.Value is Data)
{
T value = ((Data)kvp.Value).GetValueOfKey<T>(key);
if (value != null)
return value;
}
JToken token = GetValue(key);
if(token != null)
return token.ToObject<T>(); //throws exception
return default;
}
}
I can add children just fine, but my issue comes when I try to access them. An InvalidCastException is thrown within my
public T GetValueOfKey<T>(string key)
method whenever I call it using
Data
as the generic type.
For example:
Data data = GetValueOfKey<Data>("attributes");
throws an exception. I'm not sure why this is happening, so any help would be greatly appreciated!
EDIT:
Here is the complete error log thrown:
InvalidCastException: Specified cast is not valid.
(wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
Newtonsoft.Json.Linq.JToken.ToObject[T] () (at <97722d3abc9f4cf69f9e21e6770081b3>:0)
Data.GetValueOfKey[T] (System.String key) (at Assets/Scripts/Attributes/Object/Data.cs:74)
Data.AddKeyToParent (System.String parent, System.String key, Newtonsoft.Json.Linq.JToken value) (at Assets/Scripts/Attributes/Object/Data.cs:23)
DataController.Awake () (at Assets/Scripts/Controllers/DataController.cs:35)
and an example of instantiation which causes this exception:
public class DataController
{
void Awake()
{
Data data = new Data();
data.AddKey("name", "foo");
data.CreateChild("attributes");
data.AddKeyToParent("attributes", "slippery", true); //throws exception (line 35)
}
}
UPDATE (10/20/18):
Ok so I went through my code this afternoon and rewrote it as a wrapper class, now the root JObject is stored within a variable in my Data, and accessor methods adjust its properties.
However, I ran into a problem. Here's the updated class (minified to the problem):
public class Data
{
public JObject data;
public Data()
{
data = new JObject();
}
public void AddChild(string child)
{
data.Add(child, new JObject());
}
public void AddKeyWithValueToParent(string parent, string key, JToken value)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if(parentObject != null)
parentObject.Add(key, value);
}
public void AddKeyWithValue(string key, JToken value)
{
data.Add(key, value);
}
public T GetValueOfKey<T>(string key)
{
return GetValueOfKey<T>(key, data);
}
private T GetValueOfKey<T>(string key, JObject index)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
{
T value = GetValueOfKey<T>(key, kvp.Value.ToObject<JObject>());
if (value != null)
return value;
}
JToken token = index.GetValue(key);
if (token != null)
return token.ToObject<T>();
return default;
}
}
And here is an example of how to construct a Data object, and use its methods:
public class DataController
{
void Awake() {
Data data = new Data();
data.AddKeyWithValue("name", "foo");
data.AddChild("attributes");
data.AddKeyWithValueToParent("attributes", "slippery", true);
}
}
So everything in terms of adding key-value pairs, and creating children works wonderfully! No InvalidCastException at all, yay! However, when I try to serialize the object through JsonConvert.SerializeObject(data), it doesn't fully serialize it.
I have the program output to the console to show the serialization, and it looks like this:
{"data":{"name":"foo","attributes":{}}}
I've already checked to make sure that when I call data.AddKeyWithValueToParent("attributes", "slippery", true), it does indeed find the JObject value with the key attributes and even appears to successfully add the new key-value pair "slippery":true under it. But for some reason, serializing the root object data does not seem to identify that anything lies within the attributes object. Thoughts?
What I think may be happening, is that the value returned from GetValueOfKey is not acting as a reference object, but rather an entirely new object, so changes to that are not reflected within the original object.
I figured it out! I was right, the value returned from my GetValueOfKey method was returning a completely new object, and not a reference to the instance it found. Looking through my code, that should have been immediately obvious, but I'm tired and I was hoping for everything to be easy haha.
Anyway, for anyone who ever has the same question, and is just looking for a simple way to store and read some nested key-value pairs using the Json.NET library, here is the finished class that will do that (also serializable, and deserializable using JsonConvert):
public class Data
{
[JsonProperty]
private JObject data;
public Data()
{
data = new JObject();
}
public void AddChildUnderParent(string parent, string child)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if (parentObject != null)
{
parentObject.Add(child, new JObject());
ReplaceObject(parent, parentObject);
}
}
public void AddChild(string child)
{
data.Add(child, new JObject());
}
public void AddKeyWithValueToParent(string parent, string key, JToken value)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if(parentObject != null)
{
parentObject.Add(key, value);
ReplaceObject(parent, parentObject);
}
}
public void AddKeyWithValue(string key, JToken value)
{
data.Add(key, value);
}
public void RemoveKeyFromParent(string parent, string key)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if (parentObject != null)
{
parentObject.Remove(key);
ReplaceObject(parent, parentObject);
}
}
public void RemoveKey(string key)
{
data.Remove(key);
}
public T GetValueFromParent<T>(string parent, string key)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if (parentObject != null)
return parentObject.GetValue(key).ToObject<T>();
return default;
}
public T GetValueOfKey<T>(string key)
{
return GetValueOfKey<T>(key, data);
}
private T GetValueOfKey<T>(string key, JObject index)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
{
T value = GetValueOfKey<T>(key, (JObject)kvp.Value);
if (value != null)
return value;
}
JToken token = index.GetValue(key);
if (token != null)
{
data = token.Root.ToObject<JObject>();
return token.ToObject<T>();
}
return default;
}
public void ReplaceObject(string key, JObject replacement)
{
ReplaceObject(key, data, replacement);
}
private void ReplaceObject(string key, JObject index, JObject replacement)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
ReplaceObject(key, (JObject)kvp.Value, replacement);
JToken token = index.GetValue(key);
if (token != null)
{
JToken root = token.Root;
token.Replace(replacement);
data = (JObject)root;
}
}
}
That should get anyone a good head start. I plan on updating my code with params modifiers in some places to allow for multiple calls, but for now I'm just happy that I got it working. You'll notice that I had to create a ReplaceObject method, because without it, the original private JObject data was never actually updated to account for the changes made to the variable returned from GetValueOfKey.
Anyway, a big thanks to #dbc for all their help during this whole thing, and I hope this post helps someone in the future!
-ShermanZero
EDIT:
So I spent a little more time developing the class, and I think I have it pinned down to a universal point where anyone could simply copy-paste and easily implement it into their own program. Although, I personally think that #dbc has a faster solution if you care about nanosecond-millisecond differences in speed. For my own personal use though, I don't think it will make much of a difference.
Here is my full implementation, complete with documentation and error logging:
public class Data
{
[JsonExtensionData]
private JObject root;
private Texture2D texture;
private char delimiter = ',';
/// <summary>
/// Creates a new Data class with the default delimiter.
/// </summary>
public Data()
{
root = new JObject();
}
/// <summary>
/// Creates a new Data class with a specified delimiter.
/// </summary>
/// <param name="delimiter"></param>
public Data(char delimiter) : this()
{
this.delimiter = delimiter;
}
/// <summary>
/// Adds a child node to the specified parent(s) structure, which is split by the delimiter, with the specified name.
/// </summary>
/// <param name="name"></param>
/// <param name="parents"></param>
public void AddChild(string name, string parents)
{
AddChild(name, parents.Split(delimiter));
}
/// <summary>
/// Adds a child node to the specified parent(s) structure with the specified name.
/// </summary>
/// <param name="name"></param>
/// <param name="parents"></param>
public void AddChild(string name, params string[] parents)
{
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject != null)
{
parentObject.Add(name, new JObject());
ReplaceObject(lastParent, parentObject, parents);
} else
{
string message = "";
foreach (string parent in parents)
message += parent + " -> ";
throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
}
}
/// <summary>
/// Adds a child node to the root structure with the specified name.
/// </summary>
/// <param name="name"></param>
public void AddChild(string name)
{
root.Add(name, new JObject());
}
/// <summary>
/// Adds the specified key-value pair to the specified parent(s) structure, which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="parents"></param>
public void AddKeyWithValue(string key, JToken value, string parents)
{
AddKeyWithValue(key, value, parents.Split(delimiter));
}
/// <summary>
/// Adds the specified key-value pair to the specified parent(s) structure.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="parents"></param>
public void AddKeyWithValue(string key, JToken value, params string[] parents)
{
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject != null)
{
parentObject.Add(key, value);
ReplaceObject(lastParent, parentObject, parents);
} else
{
string message = "";
foreach (string parent in parents)
message += parent + " -> ";
throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
}
}
/// <summary>
/// Adds the specified key-value pair to the root structure.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void AddKeyWithValue(string key, JToken value)
{
root.Add(key, value);
}
/// <summary>
/// Removes the specified key from the specified parent(s) structure, which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
public void RemoveKey(string key, string parents)
{
RemoveKey(key, parents.Split(delimiter));
}
/// <summary>
/// Removes the specified key from the specified parent(s) structure.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
public void RemoveKey(string key, params string[] parents)
{
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject != null)
{
parentObject.Remove(key);
ReplaceObject(lastParent, parentObject, parents);
} else
{
string message = "";
foreach (string parent in parents)
message += parent + " -> ";
throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
}
}
/// <summary>
/// Removes the specified key from the root structure.
/// </summary>
/// <param name="key"></param>
public void RemoveKey(string key)
{
root.Remove(key);
}
/// <summary>
/// Returns if the specified key is contained within the parent(s) structure, which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public bool HasValue(string key, string parents)
{
return HasValue(key, parents.Split(delimiter));
}
/// <summary>
/// Returns if the specified key is contained within the parent(s) structure.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public bool HasValue(string key, params string[] parents)
{
//string lastParent = parents[parents.Length - 1];
//Array.Resize(ref parents, parents.Length - 1);
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject == null)
return false;
else if (parentObject == root && parents.Length > 0)
return false;
IDictionary<string, JToken> dictionary = parentObject;
return dictionary.ContainsKey(key);
}
/// <summary>
/// Returns the deepest parent object referenced by the parent(s).
/// </summary>
/// <param name="lastParent"></param>
/// <param name="parents"></param>
/// <returns></returns>
private JObject ReturnParentObject(out string lastParent, string[] parents)
{
lastParent = null;
if(parents.Length > 0)
{
lastParent = parents[parents.Length - 1];
Array.Resize(ref parents, parents.Length - 1);
return GetValueOfKey<JObject>(lastParent, parents);
}
return root;
}
/// <summary>
/// Returns the value of the specified key from the specified parent(s) structure, which is split by the delimiter.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public T GetValueOfKey<T>(string key, string parents)
{
return GetValueOfKey<T>(key, parents.Split(delimiter));
}
/// <summary>
/// Returns the value of the specified key from the specified parent(s) structure.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public T GetValueOfKey<T>(string key, params string[] parents)
{
JObject parentObject = null;
for(int i = 0; i < parents.Length; i++)
parentObject = GetValueOfKey<JObject>(parents[i].Trim(), parentObject == null ? root : parentObject);
return GetValueOfKey<T>(key, parentObject == null ? root : parentObject);
}
/// <summary>
/// Returns the value of the specified key from the root structure.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public T GetValueOfKey<T>(string key)
{
return GetValueOfKey<T>(key, root);
}
/// <summary>
/// Returns the value of the specified key from a given index in the structure.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="index"></param>
/// <returns></returns>
private T GetValueOfKey<T>(string key, JObject index)
{
JToken token = index.GetValue(key);
if (token != null)
return token.ToObject<T>();
foreach (var kvp in index)
if (kvp.Value is JObject)
{
T value = GetValueOfKey<T>(key, (JObject)kvp.Value);
if (value != null)
return value;
}
return default(T);
}
/// <summary>
/// Replaces an object specified by the given key and ensures object is replaced within the correct parent(s), which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="replacement"></param>
/// <param name="parents"></param>
public void ReplaceObject(string key, JObject replacement, string parents)
{
ReplaceObject(key, root, replacement, parents.Split(delimiter));
}
/// <summary>
/// Replaces an object specified by the given key and ensures object is replaced within the correct parent(s).
/// </summary>
/// <param name="key"></param>
/// <param name="replacement"></param>
/// <param name="parents"></param>
public void ReplaceObject(string key, JObject replacement, params string[] parents)
{
ReplaceObject(key, root, replacement, parents);
}
/// <summary>
/// Replaces an object specified by the given key.
/// </summary>
/// <param name="key"></param>
/// <param name="replacement"></param>
public void ReplaceObject(string key, JObject replacement)
{
ReplaceObject(key, root, replacement);
}
/// <summary>
/// Replaces an object specified by the given key within the structure and updates changes to the root node.
/// </summary>
/// <param name="key"></param>
/// <param name="index"></param>
/// <param name="replacement"></param>
private void ReplaceObject(string key, JObject index, JObject replacement)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
ReplaceObject(key, (JObject)kvp.Value, replacement);
JToken token = index.GetValue(key);
if (token != null)
{
JToken root = token.Root;
token.Replace(replacement);
this.root = (JObject)root;
}
}
/// <summary>
/// Replaces an object specified by the given key within the structure, ensuring object is replaced within the correct parent, and updates changes to the root node.
/// </summary>
/// <param name="key"></param>
/// <param name="index"></param>
/// <param name="replacement"></param>
/// <param name="parents"></param>
private void ReplaceObject(string key, JObject index, JObject replacement, params string[] parents)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
{
bool valid = false;
foreach (string str in parents)
if (str.Trim() == kvp.Key)
valid = true;
if(valid)
ReplaceObject(key, (JObject)kvp.Value, replacement);
}
JToken token = index.GetValue(key);
if (token != null)
{
JToken root = token.Root;
token.Replace(replacement);
this.root = (JObject)root;
}
}
/// <summary>
/// Returns the root structure as JSON.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return root.ToString();
}
/// <summary>
/// A ParentNotFoundException details that the supplied parent was not found within the structure.
/// </summary>
private class ParentNotFoundException : Exception
{
public ParentNotFoundException() { }
public ParentNotFoundException(string message) : base(message) { }
public ParentNotFoundException(string message, Exception inner) : base(message, inner) { }
}
}
Usage example:
Data data = new Data();
data.AddKeyWithValue("name", "foo");
data.AddChild("costs");
data.AddChild("attributes");
data.AddKeyWithValue("move", 1, "costs");
data.AddKeyWithValue("place", 2, "costs");
data.AddKeyWithValue("destroy", 3, "costs");
data.AddChild("movement", "costs");
data.AddKeyWithValue("slippery", false, "costs", "movement");
data.AddChild("movement", "attributes");
data.AddKeyWithValue("slippery", true, "attributes", "movement");
if(data.HasValue("move", "costs")) {
Debug.Log(data.GetValueOfKey<int>("move", "costs")
Debug.Log(data);
}
And its output:
1
{
"name": "foo",
"costs": {
"move": 1,
"place": 2,
"destroy": 3,
"movement": {
"slippery": false
}
},
"attributes": {
"movement": {
"slippery": true
}
}
}

Dapper/Npgsql stored procedure with refcursor parameter query

I am using Dapper ( and I couldn't be happier), I know how to access normal stored procedures as mentioned here, however, how do I pass on the the Npgsql refcursor name to the proc (using C#)? For example:
I have a proc that looks like:
FUNCTION xx.getData(
v_ref refcursor,
v_id integer)
RETURNS refcursor AS
...
How would I specify the parameters for xx.getData?
For example, if getData accepted just one parameter of type int, then I could call it like so:
var data = cnn.Query<myType>("xx.getData", new {Id = 1},
commandType: CommandType.StoredProcedure);
OR
var p = new DynamicParameters();
p.Add("#id", 11);
cnn.Execute("xx.getData", p, commandType: CommandType.StoredProcedure);
I can't find the correct type in System.DbType to pass on in the query.
Note that a refcursor corresponds to an active cursor that has already been opened in a previous call. In other words, it does not correspond to a stored procedure, but rather to a resultset (possibly but not necessarily returned from a stored procedure).
Just in case you really do need to send a refcursor, what you're looking for is NpgsqlDbType.Refcursor.
Bellow custom dynamic parameter compatible for Npgsql data types. It will work for with output refcursor parameters.
/// <summary>
/// Npgsql Dynamic Param for Dapper
/// </summary>
public class PgParam : SqlMapper.IDynamicParameters
{
private static readonly Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
private readonly Dictionary<string, ParamInfo> _parameters = new Dictionary<string, ParamInfo>();
private List<object> templates;
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
public PgParam()
{
}
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
public PgParam(object template)
{
AddDynamicParams(template);
}
/// <summary>
/// All the names of the param in the bag, use Get to yank them out
/// </summary>
public IEnumerable<string> ParameterNames
{
get { return _parameters.Select(p => p.Key); }
}
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
AddParameters(command, identity);
}
/// <summary>
/// Append a whole object full of params to the dynamic
/// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
/// </summary>
/// <param name="param"></param>
public void AddDynamicParams(
dynamic param
)
{
if (param is object obj)
{
if (!(obj is PgParam subDynamic))
{
if (!(obj is IEnumerable<KeyValuePair<string, object>> dictionary))
{
templates = templates ?? new List<object>();
templates.Add(obj);
}
else
{
foreach (var kvp in dictionary)
{
Add(kvp.Key, kvp.Value);
}
}
}
else
{
if (subDynamic._parameters != null)
foreach (var kvp in subDynamic._parameters)
_parameters.Add(kvp.Key, kvp.Value);
if (subDynamic.templates != null)
{
templates = templates ?? new List<object>();
foreach (var t in subDynamic.templates) templates.Add(t);
}
}
}
}
/// <summary>
/// Add a parameter to this dynamic parameter list
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="dbType"></param>
/// <param name="direction"></param>
/// <param name="size"></param>
public void Add(string name, object value = null, NpgsqlDbType? dbType = null, ParameterDirection? direction = null,int? size = null)
{
_parameters[name] = new ParamInfo
{
Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType,
Size = size
};
}
/// <summary>
/// Add all the parameters needed to the command just before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="identity">Information about the query</param>
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
if (templates != null)
foreach (var template in templates)
{
var newIdent = identity.ForDynamicParameters(template.GetType());
Action<IDbCommand, object> appender;
lock (paramReaderCache)
{
if (!paramReaderCache.TryGetValue(newIdent, out appender))
{
appender = SqlMapper.CreateParamInfoGenerator(newIdent, false, true);
paramReaderCache[newIdent] = appender;
}
}
appender(command, template);
}
foreach (var param in _parameters.Values)
{
var add = !((NpgsqlCommand) command).Parameters.Contains(param.Name);
NpgsqlParameter p;
if (add)
{
p = ((NpgsqlCommand) command).CreateParameter();
p.ParameterName = param.Name;
}
else
{
p = ((NpgsqlCommand) command).Parameters[param.Name];
}
var val = param.Value;
p.Value = val ?? DBNull.Value;
p.Direction = param.ParameterDirection;
if (param.Size != null) p.Size = param.Size.Value;
if (param.DbType != null) p.NpgsqlDbType = param.DbType.Value;
if (add) command.Parameters.Add(p);
param.AttachedParam = p;
}
}
/// <summary>
/// Get the value of a parameter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns>
public T Get<T>(string name)
{
var val = _parameters[name].AttachedParam.Value;
if (val == DBNull.Value)
{
if (default(T) != null)
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
return default(T);
}
return (T) val;
}
private class ParamInfo
{
public string Name { get; set; }
public object Value { get; set; }
public ParameterDirection ParameterDirection { get; set; }
public NpgsqlDbType? DbType { get; set; }
public int? Size { get; set; }
public IDbDataParameter AttachedParam { get; set; }
}
}

Refresh HttpRuntime.Cache on Azure

Is there a way that i can refresh the HttpRuntime.Cache on Azure without having to Reboot the instance?
Thanks
When you're adding items to your cache, you should add them with a CacheDependency with a global key. This allows you to have your items expire after a given time but also to clear all items linked to this key.
Here is an example implementation of a method to clear all items (CacheProvider.cs):
/// <summary>
/// Caching provider
/// </summary>
public static class CacheProvider
{
const string CacheDependencyKey = "1FADE275-2C84-4a9b-B3E1-68ABB15E53C8";
static readonly object SyncRoot = new object();
/// <summary>
/// Gets an item from cache. If the item does not exist, one will be
/// created and added to the cache.
/// </summary>
/// <param name="key">Caching key</param>
/// <param name="valueFactory">Function to create the item of it does not exist in the cache.</param>
/// <param name="expiresAfter">Time after the item wille be removed from cache.</param>
public static TValue GetOrAdd<TValue>(string key, Func<TValue> valueFactory, TimeSpan expiresAfter)
{
object itemFromCache = HttpRuntime.Cache.Get(key);
if (itemFromCache == null)
{
lock (SyncRoot)
{
itemFromCache = HttpRuntime.Cache.Get(key);
if (itemFromCache == null)
{
TValue value = valueFactory();
if (value != null)
{
if (HttpRuntime.Cache[CacheDependencyKey] == null)
HttpRuntime.Cache[CacheDependencyKey] = string.Empty;
HttpRuntime.Cache.Add(key, value, new CacheDependency(null, new string[] { CacheDependencyKey }), DateTime.Now.Add(expiresAfter), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
}
return value;
}
}
}
return (TValue)itemFromCache;
}
/// <summary>
/// Invalidate all the items from the cache.
/// </summary>
public static void InvalidateCache()
{
HttpRuntime.Cache.Remove(CacheDependencyKey);
}
}
public void Clear()
{
var itemsToRemove = new List<string>();
var enumerator = HttpContext.Current.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
itemsToRemove.Add(enumerator.Key.ToString());
}
foreach (string itemToRemove in itemsToRemove)
{
HttpContext.Current.Cache.Remove(itemToRemove);
}
}

Required Attribute in DataAnnotations Does Not seem to work

I have a DataAnnotationValidator that I created. I am currently trying to test it with a Required Field attribute and I can't get the IsValid property to fail when my property is null. It does work correctly when I create a number with a Range attribute that is outside of the specified Range.
public class TestEntityWithDataAnnotations
{
public Guid Id { get; set; }
[Required(ErrorMessage = "Required")]
public string Name { get; set; }
}
[TestFixture]
public class DataAnnotationValidatorTest
{
[Test]
public void Validate_ReturnsFailure_WhenPropertyValidationIsNotValid()
{
var validator = new DataAnnotationValidator();
var invalidEntity = new TestEntityWithDataAnnotations
{
Id = Guid.NewGuid()
};
var validationResult = validator.Validate(invalidEntity);
Assert.IsFalse(validationResult.IsValid);
}
}
public class DataAnnotationValidator
{
public ValidationResult Validate(object obj)
{
Type objType = obj.GetType();
var typeDescriptor = GetTypeDescriptor(obj, objType);
var validationResult = new ValidationResult();
var classValidationResult = CheckClassIsValid(obj, typeDescriptor);
if (!classValidationResult.IsValid)
{
validationResult.AddErrors(classValidationResult.Errors);
}
foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties())
{
// Loop over all of the properties on our object that have Validation Attributes
var propValidationResult = CheckPropertyIsValid(obj, propertyDescriptor);
if(!propValidationResult.IsValid)
{
validationResult.AddErrors(propValidationResult.Errors);
}
}
return validationResult;
}
/// <summary>
/// Checks to see if there are any class level validation attributes and runs them
/// </summary>
/// <returns></returns>
private static ValidationResult CheckClassIsValid(object obj, ICustomTypeDescriptor typeDescriptor)
{
var errors = typeDescriptor.GetAttributes().OfType<ValidationAttribute>()
.Where(x => !x.IsValid(obj))
.Select(x => new ValidationError(typeDescriptor.GetClassName(), x.ErrorMessage));
return new ValidationResult(errors.ToList());
}
/// <summary>
/// Checks to see if a property has any DataAnnotations that it has violated
/// </summary>
private static ValidationResult CheckPropertyIsValid(object obj, PropertyDescriptor propertyDescriptor)
{
var errors = propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(x => !x.IsValid(obj))
.Select(x => new ValidationError(propertyDescriptor.Name, x.ErrorMessage));
return new ValidationResult(errors.ToList());
}
/// <summary>
/// Gets the model's type descriptor. In order to support the buddy class metadata model
/// for LINQ to SQL and Entity Framework, it uses
/// <see cref="AssociatedMetadataTypeTypeDescriptionProvider"/>.
/// </summary>
/// <param name="obj">The model object</param>
/// <param name="objType">The type of the model object</param>
/// <returns>The model's type descriptor</returns>
private static ICustomTypeDescriptor GetTypeDescriptor(object obj, Type objType)
{
var provider = new AssociatedMetadataTypeTypeDescriptionProvider(objType);
return provider.GetTypeDescriptor(objType, obj);
}
}
A bit of stupidity on my part. I needed to pass the value of the property into IsValid inside of CheckPropertyIsValid instead of the whole object.
private static ValidationResult CheckPropertyIsValid(object obj, PropertyDescriptor propertyDescriptor)
{
var errors = propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(x => !x.IsValid(propertyDescriptor.GetValue(obj)))
.Select(x => new ValidationError(propertyDescriptor.Name, x.ErrorMessage));
return new ValidationResult(errors.ToList());
}

Converting a generic list to a CSV string

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

Categories

Resources