Parse public fields (not only public properties) to allow compatibility with Vb.net withtout explicitely setting Property tags on field
Excel Type
- [X] XLSX
- [ ] XLSM
- [ ] CSV
- [ ] OTHER
Upload Excel File
How to reproduce: .net framework 4.8 project
Basic template file, two strings titles in the header, and one line rows.
mini_TotalEnVigueur.xlsx
MiniExcel Version
1.30.3, likely to affect previous versions since April 17 2021
Description
MiniExcel.SaveAsByTemplate with a dictionnary object
> MiniExcel\src\MiniExcel\OpenXml\ExcelOpenXmlTemplate.Impl.cs
On line 859, property with s.Name = "type" is being added added twice to the dictionary
System.ArgumentException: An item with the same key has already been added.
Change introduced added April 17 2021
xRowInfo.PropsMap = xRowInfo.IEnumerableGenricType.GetProperties()
.ToDictionary(s => s.Name, s => new PropInfo { PropertyInfo = s, UnderlyingTypePropType = Nullable.GetUnderlyingType(s.PropertyType) ?? s.PropertyType })
Here is the datamodel I send (it's in VB, and nested type are not seen from reflection, so I send it as a dictionnary instead:
Public Class EnVigueurDataModel
Public TitreTypeAssurance As String
Public TitreDateRapport As String
Public Class EnVigueurLigne
Public PoliceNumero As String
<ExcelFormat("YYYY-mm-dd")>
Public DateEntreeEnVigueur As Date
Public Assure As String
Public Sexe As String
<ExcelFormat("YYYY-mm-dd")>
Public DateNaissance As String
Public Tabagisme As String
Public Statut As String
Public Vendeur As String
<ExcelFormat("YYYY-mm-dd")>
Public DateChangementStatut As String
<ExcelFormat("# ##0.00 $")>
Public CapitalAssure As String 'montantCouverture
<ExcelFormat("# ##0.00 $")>
Public Prime As Decimal
<ExcelFormat("# ##0.00 $")>
Public CapitalAssureDa As Decimal = 0.0
<ExcelFormat("# ##0.00 $")>
Public PrimeDa As Decimal = 0.0
<ExcelFormat("# ##0.00 $")>
Public PrimeTotale As Decimal
End Class
Public Lignes As IEnumerable(Of EnVigueurLigne)
End Class
MiniExcel.SaveAsByTemplate(nomFichierDestination, templatePath, DataModel.ToDictionary(Of Object))
ToDictionnary is an extension method (with newtonsoft json) to serialise unserialize object as a dictionnary :
Public Module DictionaryExtensions
<Extension()>
Public Function ToDictionary(Of TValue)(obj As Object) As Dictionary(Of String, TValue)
Dim json = JsonConvert.SerializeObject(obj)
Dim dictionary = JsonConvert.DeserializeObject(Of Dictionary(Of String, TValue))(json)
Return dictionary
End Function
End Module
update, I removed the toDictionary() on my model and used poco reflection method after debuging the library. turns out class attributes in vb.net are fields and not automagically properties. I've been using C# for so long I forgot the compilier handles it for you.
To fix my issue, I needed to declare fields as Property for the library to map them through reflection (type.GetProperties),
i.e.
Public Property PoliceNumero As String
maybe the library should consider adding type.GetFields to map data types (there's 6-7 areas in the code to fix) including this part in MiniExcel\src\MiniExcel\OpenXml\ExcelOpenXmlTemplate.cs
(...)
public void SaveAsByTemplateImpl(Stream templateStream, object value)
{
//only support xlsx
Dictionary<string, object> values = null;
if (value is Dictionary<string, object>)
{
values = value as Dictionary<string, object>;
foreach (var key in values.Keys)
{
var v = values[key];
if (v is IDataReader)
{
values[key] = TypeHelper.ConvertToEnumerableDictionary(v as IDataReader).ToList();
}
}
}
else
{
var type = value.GetType();
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
values = new Dictionary<string, object>();
foreach (var p in props)
{
values.Add(p.Name, p.GetValue(value));
}
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var f in fields)
{
if (!values.ContainsKey(f.Name))
{ values.Add(f.Name, f.GetValue(value)); }
}
}