using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Excel; using System.IO; namespace NP.Base.Lib { /// /// Main Class that converts a Excel file to IEnumerable of the specified entity class /// public class ExcelToEntity : IDisposable { /// /// The excel table has row header with the information /// public bool HasHeaders { get; set; } /// /// Excel Data Reader Interface to convert excel to data table /// IExcelDataReader edr; /// /// Manual contructor by File stream and excel version type /// /// Can be a memory stream or file stream or byte array /// Excel type of the file given (binary xls or open format xlsx) public ExcelToEntity(Stream s, ExcelType et) { HasHeaders = true; switch (et) { case ExcelType.xlsx: edr = ExcelReaderFactory.CreateOpenXmlReader(s); break; case ExcelType.xls: edr = ExcelReaderFactory.CreateBinaryReader(s); break; default: break; } edr.IsFirstRowAsColumnNames = false; } /// /// Automatic constructor based on html form post multipart input type file /// /// posted file from asp html request or MVC post action public ExcelToEntity(System.Web.HttpPostedFileBase File) { if (File == null) throw new ArgumentNullException("HttpPostedFileBase File is null"); HasHeaders = true; if (File.FileName.Trim().ToLower().EndsWith(".xlsx")) edr = ExcelReaderFactory.CreateOpenXmlReader(File.InputStream); else edr = ExcelReaderFactory.CreateBinaryReader(File.InputStream); edr.IsFirstRowAsColumnNames = false; } /// /// Read the data table obtain from the converted excel file and transform to the entity class /// /// Entity Class to transform to /// Start row from the data table /// Start column from the data table /// sheet name where to read the data /// public IEnumerable Read(int StartRow = 1, int StartColumn = 1, string SheetName = null) where T : new() { var dt = getTable(StartRow, StartColumn, SheetName); if (!HasHeaders) StartRow--; for (int i = StartRow; i < dt.Rows.Count; i++) { var r = dt.Rows[i]; if (!(r.ItemArray.All(x => string.IsNullOrWhiteSpace(x.ToString())))) yield return GetEntity(r); } } /// /// internal method to transform the excel file to a data table /// /// Start row from the data table /// Start column from the data table /// sheet name where to read the data /// private System.Data.DataTable getTable(int startRow, int startCol, string sheetName) { var ds = edr.AsDataSet(); System.Data.DataTable dt; if (!string.IsNullOrEmpty(sheetName)) { dt = ds.Tables[sheetName]; } else { dt = ds.Tables[0]; } if (HasHeaders) { var drHeader = dt.Rows[startRow - 1]; for (int i = startCol - 1; i < dt.Columns.Count; i++) { var name = drHeader[i]; if (name != null && name is string) { var cname = new String(((string)name).Where(c => char.IsLetterOrDigit(c)).ToArray()); if (char.IsDigit(cname.FirstOrDefault())) cname = "_" + cname; if (!string.IsNullOrWhiteSpace(cname)) { try { dt.Columns[i].ColumnName = cname; } catch (System.Data.DuplicateNameException) { dt.Columns[i].ColumnName = cname + "_" + i.ToString(); } } }; } } return dt; } /// /// internal method to convert a data row to a data entity /// /// Entity Class /// Data row to grab the data from /// private t GetEntity(System.Data.DataRow row) where t : new() { var entity = new t(); var properties = typeof(t).GetProperties(); foreach (var property in properties.Where(x => x.GetCustomAttributes(typeof(ExcelNotMapAttribute), true).SingleOrDefault() == null)) { //Get the description attribute var CustomAttribute = (ExcelColumnAttribute)property.GetCustomAttributes(typeof(ExcelColumnAttribute), true).SingleOrDefault(); string propName = CustomAttribute == null ? property.Name : CustomAttribute.Name; object v = null; try { v = row[propName]; } catch (Exception) { continue; } try { if (v.GetType() == property.PropertyType) { property.SetValue(entity, v); } else { if (property.PropertyType == typeof(DateTime?)) { if (DateTime.TryParse(row[propName].ToString(), out DateTime dt)) { property.SetValue(entity, dt); } else { property.SetValue(entity, null); } } else { var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; property.SetValue(entity, Convert.ChangeType(row[propName], propertyType)); } } } catch (Exception) { throw new InvalidCastException("Unable to cast value '" + v.ToString() + "' to type '" + property.PropertyType.Name + "' for property: " + property.Name + " row: " + row.ItemArray.Aggregate((a, b) => a.ToString() + "," + b.ToString())); } } return entity; } /// /// implement the garbage colector and close the unmanaged excel to data table reader /// public void Dispose() { if (!edr.IsClosed) { edr.Close(); } } } /// /// Attribute to map a property to a column field in the excel file /// public class ExcelColumnAttribute : Attribute { public ExcelColumnAttribute() { } /// /// Attribute to map a property to a column field in the excel file /// /// Name of the column in the excel file public ExcelColumnAttribute(string Name) { this.Name = Name; } public string Name { get; set; } } /// /// Attribute to skip the mapping of a property in the selected entity class /// public class ExcelNotMapAttribute : Attribute { public ExcelNotMapAttribute() { } } /// /// Possible excel file types supported by the reader /// public enum ExcelType { /// /// Microsoft Office Excel 2007 and later Open XML Format /// xlsx, /// /// Microsoft Office Excel 2003 and earlier propiertary binary format /// xls } }