技术点:
1.自定义
attribute
属性
2.通过反射取类及其属性的attribute
属性值
3.NPOI包常用属性及方法(我也仅仅知道用到过的,陌生的要么见名知意,要么百度查)
实现功能点:
List类对象的模板导出,实用场景例子见最后代码块
(emm...还是比较抽象,代码见)
EXCEL导出类DTO超类
public abstract class ExcelSuper
{
// 所有excel导出类DTO必须要继承的方法
// 限制比较弱,主要还是用来区分DTO用在何处
}
定义继承导出类DTO的特性说明类
/// <summary>
/// 导出类 类特性
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ExcelExpClassAttribute : Attribute
{
public ExcelExpClassAttribute(int colSplit, int rowSplit, int leftmostColumn, int topRow)
{
this.colSplit = colSplit;
this.rowSplit = rowSplit;
this.leftmostColumn = leftmostColumn;
this.topRow = topRow;
}
/// <summary>
/// 冻结的列数
/// </summary>
public int colSplit { get; set; }
/// <summary>
/// 冻结的行数
/// 只冻结列时为0
/// </summary>
public int rowSplit { get; set; }
/// <summary>
/// 右边区域可见的首列序号,从1开始计算
/// </summary>
public int leftmostColumn { get; set; }
/// <summary>
/// 边区域可见的首行序号,从1开始计算,
/// 只冻结列时为0
/// </summary>
public int topRow { get; set; }
}
/// <summary>
/// 导出类 属性特性
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ExcelExpAttribute : Attribute
{
/// <summary>
/// 是否隐藏,与SortIndex搭配使用
/// </summary>
public bool IsHidden { get; set; } = false;
/// <summary>
/// 排序索引(保持连贯性、不可重复)
/// - 对应dataTable的列排序 [0,1...]
/// - 对应Excel的列索引 [0,1...]
/// </summary>
public int SortIndex { get; set; }
/// <summary>
/// Excel列名
/// </summary>
public string ColName { get; set; }
/// <summary>
/// 是否动态列
/// </summary>
public bool IsdynamicColName { get; set; }
/// <summary>
/// 是否合并行 -- 预留,后补
/// </summary>
public bool IsRowMerge { get; set; } = false;
/// <summary>
/// 合并行依据 -- 预留,后补
/// </summary>
public string RowMergeBasis { get; set; }
}
Excel帮助类
/// <summary>
/// Excel帮助类
/// </summary>
public static class ExcelHelper
{
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="workbook"></param>
/// <param name="dtSource"></param>
/// <param name="sheetNum"></param>
/// <param name="useAttributeColName"> 添加一行列名,取类自定义属性列名称:ExcelExpAttribute.ColName</param>
/// <returns></returns>
private static IWorkbook OutputExcel<T>(this IWorkbook workbook, IEnumerable<T> dtSource, int sheetNum, bool useAttributeColName = false)
{
// 读取sheet
ISheet sheet = workbook.GetSheetAt(sheetNum);
int rowIndex = sheet.LastRowNum + 1;// 获取写入行初始值
if (useAttributeColName)
{
// 待补充
}
Type objType = typeof(T);
// 取类上的自定义特性
object[] objs = objType.GetCustomAttributes(typeof(ExcelExpClassAttribute), true);
foreach (object obj in objs)
{
ExcelExpClassAttribute attr = obj as ExcelExpClassAttribute;
if (attr != null)
{
sheet.CreateFreezePane(attr.colSplit, attr.rowSplit, attr.leftmostColumn, attr.topRow);// 设置冻结行、列
break;
}
}
// 循环添加数据
foreach (T item in dtSource)
{
IRow rowi = sheet.CreateRow(rowIndex);
// 设置自适应宽度,9为Excel列数,根据需要自已修改
for (int columnNum = 0; columnNum <= rowi.LastCellNum; columnNum++)
{
int columnWidth = sheet.GetColumnWidth(columnNum) / 256;
sheet.SetColumnWidth(columnNum, columnWidth * 256);
}
// 取属性上的自定义特性
foreach (PropertyInfo propInfo in objType.GetProperties())
{
object[] objAttrs = propInfo.GetCustomAttributes(typeof(ExcelExpAttribute), true);
if (objAttrs.Length > 0)
{
ExcelExpAttribute attr = objAttrs[0] as ExcelExpAttribute;
if (attr != null)
{
if (attr.IsHidden)
continue;
int colIndex = attr.SortIndex;
var name = propInfo.Name;// 实例名称
var value = propInfo.GetValue(item);// 实例值
#region 判断值类型并填充
var newCell = rowi.CreateCell(colIndex);
switch (propInfo.PropertyType.ToString())
{
case "System.String"://字符串类型
newCell.SetCellValue(value == null ? "" : value.ToString());
break;
case "System.DateTime"://日期类型
DateTime.TryParse(value.ToString(), out DateTime dateV);
newCell.SetCellValue(dateV);
break;
case "System.Boolean"://布尔型
bool.TryParse(value.ToString(), out bool boolV);
newCell.SetCellValue(boolV);
break;
case "System.Int16"://整型
case "System.Int32":
case "System.Int64":
case "System.Byte":
int.TryParse(value.ToString(), out int intV);
newCell.SetCellValue(intV);
break;
case "System.Decimal"://浮点型
case "System.Double":
double.TryParse(value.ToString(), out double doubV);
newCell.SetCellValue(doubV);
break;
case "System.DBNull"://空值处理
newCell.SetCellValue("");
break;
default:
newCell.SetCellValue("");
break;
}
#endregion
}
}
}
rowIndex++;
}
return workbook;
}
/// <summary>
/// 导出模板Excel
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="iEnumerable">数据源</param>
/// <param name="fileFullPath">文件全路径(包含文件及后缀名)</param>
/// <returns>文件流</returns>
public static MemoryStream ExportToExcel<T>(IEnumerable<T> iEnumerable, string fileFullPath)
where T : ExcelSuper
{
// 打开模板文件并写入
var workbook = GetIWorkbook(fileFullPath, out ExcelTypeEnum type);
if (type == ExcelTypeEnum.XLS)
{
workbook = (HSSFWorkbook)workbook.OutputExcel(iEnumerable, 0, false);
}
else
{
workbook = (XSSFWorkbook)workbook.OutputExcel(iEnumerable, 0, false);
}
try
{
using (MemoryStream ms = new MemoryStream())
{
workbook.Write(ms);
ms.Flush();
ms.Position = 0;
// sheet.Dispose();
// workbook.Dispose();//一般只用写这一个就OK了,他会遍历并释放所有资源,但当前版本有问题所以只释放sheet
return ms;
}
}
catch (Exception ex)
{
throw ex;
}
}
#region Private method
private static IWorkbook GetIWorkbook(string fileFullPath, out ExcelTypeEnum type)
{
string filename = Path.GetFileNameWithoutExtension(fileFullPath);// 文件名称
string extension = Path.GetExtension(fileFullPath);// 后缀名 带点(.)
try
{
if (!File.Exists(fileFullPath))
{
throw new Exception($"模板:{filename + extension}不存在");
}
FileStream fs = new FileStream(fileFullPath, FileMode.Open, FileAccess.ReadWrite);
if (".xls".Equals(extension.ToLower()))
{
type = ExcelTypeEnum.XLS;
return new HSSFWorkbook(fs);// Excel2003以前版本
}
else
{
type = ExcelTypeEnum.XLSX;
return new XSSFWorkbook(fs);// Excel2007后的版本
}
}
catch (Exception ex)
{
throw new Exception("获取文件错误:" + ex);
}
}
#endregion
enum ExcelTypeEnum
{
XLS,
XLSX
}
}
这部分要讲的点其实挺多的,关键就是EXCEL导出所用到的数据源是强类型的。
可以看出来list
其实是EF的Queryable
toList()
后的类集合,作为数据源存在;// 比较喜欢强类型,直接点出来的属性让人放心ヾ(•ω•`)o里面的DTO
DesWeeklyReportExcExp
继承ExcelSuper
,特性分别加在类及属性上。
public class XXXXController : CoreController
{
// 控制器内部
[HttpPost]
public ActionResult export()
{
// 控制器接口
var list = op
.GetPagedQuery(PageModel)
.Select(s => new DesWeeklyReportExcExp
{
col1 = s.Project.ProjName,
col2 = s.ColAttROPDate1?.ToString("yyyy.MM.dd"),
col3 = (s.ColAttROPDate2 == null ? "无" : s.ColAttROPDate2.Value.ToString("yyyy.MM.dd"))
+ "/"
+ (s.ColAttROPDate3 == null ? "无" : s.ColAttROPDate3.Value.ToString("yyyy.MM.dd")),
col4 = s.ColAttROPDate4?.ToString("yyyy.MM.dd")
}).ToList();
string filePath = Server.MapPath("~/download/[这是模板名称].xlsx");
string filename = Path.GetFileNameWithoutExtension(filePath);// 文件名称
string extension = Path.GetExtension(filePath);// 后缀名 带点(.)
string fileDownloadName = filename + extension;
var fs = ExcelHelper.ExportToExcel(list, filePath).ToArray();
return File(fs, "application/ms-excel", fileDownloadName);
}
}
[ExcelExpClassAttribute(2, 0, 2, 0)]
public class DesWeeklyReportExcExp : ExcelSuper
{
/// <summary>
/// 列1
/// </summary>
[ExcelExp(SortIndex = 0, ColName = "列1")]
public string col1 { get; set; }
/// <summary>
/// 列2
/// </summary>
[ExcelExp(SortIndex = 0, ColName = "列2")]
public string col2 { get; set; }
/// <summary>
/// 列3
/// </summary>
[ExcelExp(SortIndex = 0, ColName = "列3")]
public string col3 { get; set; }
/// <summary>
/// 列4
/// </summary>
[ExcelExp(SortIndex = 0, ColName = "列4")]
public string col4 { get; set; }
}
部分拙见,大部分还需要补充,比如设置合并列,比如数据源支持DataTable导出等等,还有现有的代码可能不够完善,看到的多多提下宝贵意见吧