How do you use CsvHelper to write a class derived from DynamicObject?
I was hoping to use a dynamically typed object to write to a CSV file.
I am getting "CsvHelper.CsvWriterException" in the CsvWriter.WriteObject method with this message: "No properties are displayed for type" WpmExport.DynamicEntry ".
Here is the class I'm trying to use:
public class DynamicEntry : DynamicObject
{
private Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
string name = binder.Name.ToLower();
return dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
dictionary[binder.Name.ToLower()] = value;
return true;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return dictionary.Keys.AsEnumerable();
}
}
Anyone with ideas or working examples? The documentation at http://joshclose.github.io/CsvHelper/ suggests that this is possible, but does not provide any guidance.
TIA
source to share
The functionality doesn't exist yet. You can write dynamic
, but not DynamicObject
. You can see a thread on this topic. https://github.com/JoshClose/CsvHelper/issues/187
When the functionality is implemented, I will update the answer to the version it is in.
Update
This functionality will be available in version 3.0. You can try the 3.0 beta from NuGet.
source to share
Since I can't wait for version 3.0 (and CsvHelper.Excel to support it), I found an intermediate solution.
Got a class for export:
public partial class EntryReportInventory
{
public Guid DeviceId { get; set; }
[ReportProperty]
public string DeviceName { get; set; }
public Dictionary<string, object> InventoryValues { get; set; }
public EntryReportInventory(Device device, Dictionary<string, object> inventoryValues)
{
this.DeviceId = device.Id;
this.DeviceName = device.Name;
this.InventoryValues = inventoryValues;
}
}
Created cartographer:
Type genericClass = typeof(DefaultCsvClassMap<>);
Type constructedClass = genericClass.MakeGenericType(typeof(EntryReportInventory));
return (CsvClassMap)Activator.CreateInstance(constructedClass);
And now the magic. I am iterating over all properties.
foreach (PropertyInfo property in mapping)
{
...
if (isInventoryReportBaseType && typeof(Dictionary<string, object>).IsAssignableFrom(property.PropertyType))
{
var dataSource = (ReportInventoryBase)Activator.CreateInstance(entityType, dbContext);
foreach (var item in dataSource.ColumnNameAndText)
{
var columnName = item.Key;
var newMap = new CsvPropertyMap(property);
newMap.Name(columnName);
newMap.TypeConverter(new InventoryEntryListSpecifiedTypeConverter(item.Key));
customMap.PropertyMaps.Add(newMap);
}
...
}
And my converter:
public class InventoryEntryListSpecifiedTypeConverter : CsvHelper.TypeConversion.ITypeConverter
{
private string indexKey;
public InventoryEntryListSpecifiedTypeConverter(string indexKey)
{
this.indexKey = indexKey;
}
public bool CanConvertFrom(Type type)
{
return true;
}
public bool CanConvertTo(Type type)
{
return true;
}
public object ConvertFromString(TypeConverterOptions options, string text)
{
throw new NotImplementedException();
}
public string ConvertToString(TypeConverterOptions options, object value)
{
var myValue = value as Dictionary<string, object>;
if (value == null || myValue.Count == 0) return null;
return myValue[indexKey] + "";
}
}
Not sure why, but it works to pass the same property multiple times. This :) You only need to have a list before (here: dataSource.ColumnNameAndText populated from an external source) to identify the columns / values.
source to share