分享:一个基于NPOI的excel导入导出组件(强类型)
一、引子
新进公司被安排处理系统的数据报表任务——对学生的考试成绩进行统计并能导出到excel。虽然以前也有弄过,但感觉不是很好,所以这次狠下心,多花点时间作个让自己满意的插件。
二、适用领域
因为需求是基于学生成绩,可能更多的是按这样的需求去考虑。如下图(请不要计较数据):

三、逻辑
一个excel文件 --> N个工作表 --> N个数据容器-->N个数据内容
四、类的组成
| WorkbookWrapper(抽象类) | excel容器,一个实例代表一个excel文件 |
| BuildContext(数据上下文) | 在事件中获取对象的上下文 |
| WorkbookExtensions(扩展类) | WorkbookWrapper的扩展,有2个方法,一个保存到本地,一个是http下载 |
| XSSFWorkbookBuilder(Excel2007) | 继承WorkbookWrapper提供2007的版本的实现类 |
| HSSFWorkbookBuilder(Excel2003) | 同上,版本为2003 |
| ExcelModelsPropertyManage | 对生成的的数据结构的管理类 |
| ISheetDetail(工作表接口) | 每一个ISheetDetail都代表一张工作表(包含一个SheetDataCollection) |
| ISheetDataWrapper(内容容器接口) | 每一个ISheetDataWrapper都代表ISheetDetail里的一块内容 |
| SheetDataCollection(数据集合) | 内容容器的集合 |
| IExcelModelBase(内容模型的基类接口) | ISheetDataWrapper里的内容数据模型均继承此接口(包含一个IExtendedBase集合) |
| IExtendedBase(扩展内容接口) | 如上图中的科目1-科目3属于不确定数量的内容均继承此接口 |
| IgnoreAttribute(忽略标记) | 不想输出到excel的打上此标记即可 |
| CellExtensions(列的扩展) | 格式化列的样式 |
| EnumService(枚举服务类) | 输出枚举对象里的DescriptionAttribute特性的值 |
注:标题是依据模型属性的 DisplayName 特性标记来实现的。
五、主要实现类
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ComponentModel;
using System.Collections; namespace ExcelHelper.Operating
{
public abstract class WorkbookBuilder
{
protected WorkbookBuilder()
{
currentWorkbook = CreateWorkbook(); buildContext = new BuildContext() { WorkbookBuilder = this, Workbook = currentWorkbook };
} public delegate void BuildEventHandler(BuildContext context); protected abstract IWorkbook CreateWorkbook(); public IWorkbook currentWorkbook; private ICellStyle _centerStyle; public ICellStyle CenterStyle
{
get
{
if (_centerStyle == null)
{
_centerStyle = currentWorkbook.CreateCellStyle(); _centerStyle.Alignment = HorizontalAlignment.Center; _centerStyle.VerticalAlignment = VerticalAlignment.Center;
} return _centerStyle;
}
} private Int32 StartRow = ;//起始行 private BuildContext buildContext; public event BuildEventHandler OnHeadCellSetAfter; public event BuildEventHandler OnContentCellSetAfter; #region DataTableToExcel public void Insert(ISheetDetail sheetDetail)
{
ISheet sheet; if (sheetDetail.IsContinue)
{
sheet = currentWorkbook.GetSheetAt(currentWorkbook.NumberOfSheets - ); StartRow = sheet.LastRowNum + ;
}
else
{
sheet = currentWorkbook.CreateSheet(sheetDetail.SheetName);
} buildContext.Sheet = sheet; sheet = DataToSheet(sheetDetail.SheetDetailDataWrappers, sheet); }
/// <summary>
/// 这里添加数据,循环添加,主要应对由多个组成的
/// </summary>
/// <param name="sheetDetailDataWrappers"></param>
/// <param name="sheet"></param>
/// <returns></returns>
private ISheet DataToSheet(SheetDataCollection sheetDetailDataWrappers, ISheet sheet)
{
foreach (var sheetDetailDataWrapper in sheetDetailDataWrappers)
{
if (sheetDetailDataWrapper.Datas == null || sheetDetailDataWrapper.Datas.Count() == )
{
continue;
} Type type = sheetDetailDataWrapper.Datas.GetType().GetGenericArguments()[]; if (sheetDetailDataWrapper.HaveTitle)
{
sheet = SetTitle(sheet, sheetDetailDataWrapper, type);
} sheet = AddValue(sheet, sheetDetailDataWrapper, type); StartRow = StartRow + sheetDetailDataWrapper.EmptyIntervalRow;
} return sheet;
} #endregion #region 设置值 private void SetCellValue(ICell cell, object obj)
{
if (obj == null)
{
cell.SetCellValue(" "); return;
} if (obj is String)
{
cell.SetCellValue(obj.ToString()); return;
} if (obj is Int32 || obj is Double)
{
cell.SetCellValue(Math.Round(Double.Parse(obj.ToString()), )); return;
} if (obj.GetType().IsEnum)
{
cell.SetCellValue(EnumService.GetDescription((Enum)obj)); return;
} if (obj is DateTime)
{
cell.SetCellValue(((DateTime)obj).ToString("yyyy-MM-dd HH:mm:ss")); return;
} if (obj is Boolean)
{
cell.SetCellValue((Boolean)obj ? "√" : "×"); return;
}
} #endregion #region SetTitle
private ISheet SetTitle(ISheet sheet, ISheetDataWrapper sheetDetailDataWrapper, Type type)
{
IRow titleRow = null; ICell titleCell = null; if (!String.IsNullOrEmpty(sheetDetailDataWrapper.DataName))
{
titleRow = sheet.CreateRow(StartRow); buildContext.Row = titleRow; StartRow++; titleCell = SetCell(titleRow, , sheetDetailDataWrapper.DataName); if (OnHeadCellSetAfter != null)
{
OnHeadCellSetAfter(buildContext);
}
} IRow row = sheet.CreateRow(StartRow); buildContext.Row = row; IList<PropertyInfo> checkPropertyInfos = ExcelModelsPropertyManage.CreatePropertyInfos(type); int i = ; foreach (PropertyInfo property in checkPropertyInfos)
{
DisplayNameAttribute dn = property.GetCustomAttributes(typeof(DisplayNameAttribute), false).SingleOrDefault() as DisplayNameAttribute; if (dn != null)
{
SetCell(row, i++, dn.DisplayName);
continue;
} Type t = property.PropertyType; if (t.IsGenericType)
{
if (sheetDetailDataWrapper.Titles == null || sheetDetailDataWrapper.Titles.Count() == )
{
continue;
} foreach (var item in sheetDetailDataWrapper.Titles)
{
SetCell(row, i++, item.TypeName);
}
}
} if (titleCell != null && i > )
{
titleCell.MergeTo(titleRow.CreateCell(i - )); titleCell.CellStyle = this.CenterStyle;
} StartRow++; return sheet;
}
#endregion #region AddValue
private ISheet AddValue(ISheet sheet, ISheetDataWrapper sheetDetailDataWrapper, Type type)
{
IList<PropertyInfo> checkPropertyInfos = ExcelModelsPropertyManage.CreatePropertyInfos(type); Int32 cellCount = ; foreach (var item in sheetDetailDataWrapper.Datas)
{
if (item == null)
{
StartRow++;
continue;
} IRow newRow = sheet.CreateRow(StartRow); buildContext.Row = newRow; foreach (PropertyInfo property in checkPropertyInfos)
{
Object obj = property.GetValue(item, null); Type t = property.PropertyType; if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var ssd = ((IEnumerable)obj).Cast<IExtendedBase>(); if (ssd == null)
{
continue;
} foreach (var v in sheetDetailDataWrapper.Titles)
{
IExtendedBase sv = ssd.Where(s => s.TypeId == v.TypeId).SingleOrDefault(); SetCell(newRow, cellCount++, sv.TypeValue);
} continue;
} SetCell(newRow, cellCount++, obj);
} StartRow++;
cellCount = ;
} return sheet;
} #endregion #region 设置单元格
/// <summary>
/// 设置单元格
/// </summary>
/// <param name="row"></param>
/// <param name="index"></param>
/// <param name="value"></param>
/// <returns></returns>
private ICell SetCell(IRow row, int index, object value)
{
ICell cell = row.CreateCell(index); SetCellValue(cell, value); buildContext.Cell = cell; if (OnContentCellSetAfter != null)
{
OnContentCellSetAfter(buildContext);
} return cell;
}
#endregion #region ExcelToDataTable /// <summary>
/// 导入
/// </summary>
/// <typeparam name="T">具体对象</typeparam>
/// <param name="fs"></param>
/// <param name="fileName"></param>
/// <param name="isFirstRowColumn"></param>
/// <returns></returns>
public static IEnumerable<T> ExcelToDataTable<T>(Stream fs, bool isFirstRowColumn = false) where T : new()
{
List<T> ts = new List<T>(); Type type = typeof(T); IList<PropertyInfo> checkPropertyInfos = ExcelModelsPropertyManage.CreatePropertyInfos(type); try
{
IWorkbook workbook = WorkbookFactory.Create(fs); fs.Dispose(); ISheet sheet = workbook.GetSheetAt(); if (sheet != null)
{
IRow firstRow = sheet.GetRow(); int cellCount = firstRow.LastCellNum; //一行最后一个cell的编号 即总的列数 Int32 startRow = isFirstRowColumn ? : ; int rowCount = sheet.LastRowNum; //行数 int length = checkPropertyInfos.Count; length = length > cellCount + ? cellCount + : length; Boolean haveValue = false; for (int i = startRow; i <= rowCount; ++i)
{
IRow row = sheet.GetRow(i); if (row == null) continue; //没有数据的行默认是null T t = new T(); for (int f = ; f < length; f++)
{
ICell cell = row.GetCell(f); if (cell == null || String.IsNullOrEmpty(cell.ToString()))
{
continue;
} object b = cell.ToString(); if (cell.CellType == CellType.Numeric)
{
//NPOI中数字和日期都是NUMERIC类型的,这里对其进行判断是否是日期类型
if (HSSFDateUtil.IsCellDateFormatted(cell))//日期类型
{
b = cell.DateCellValue;
}
else
{
b = cell.NumericCellValue;
}
} PropertyInfo pinfo = checkPropertyInfos[f]; if (pinfo.PropertyType.Name != b.GetType().Name) //类型不一样的时候,强转
{
b = System.ComponentModel.TypeDescriptor.GetConverter(pinfo.PropertyType).ConvertFrom(b.ToString());
} type.GetProperty(pinfo.Name).SetValue(t, b, null); if (!haveValue)
{
haveValue = true;
}
}
if (haveValue)
{
ts.Add(t); haveValue = false;
}
}
} return ts;
}
catch (Exception ex)
{
return null;
}
} #endregion
} public class BuildContext
{
public WorkbookBuilder WorkbookBuilder { get; set; } public IWorkbook Workbook { get; set; } public ISheet Sheet { get; set; } public IRow Row { get; set; } public ICell Cell { get; set; } }
}
六、总结
看似简单的逻辑在具体实施还是会碰到的许多问题,尤其是NPOI的数据类型与想要的类型的不符的处理;通用的实现等等,不过幸运的是最后还是出一个满意的版本,这应该算自己第一个面向接口的编程的例子了。
如果你发现什么问题或者有更好的实现方式麻烦留言或者与我联系!
项目地址:https://github.com/aa317016589/ExcelHelper/
分享:一个基于NPOI的excel导入导出组件(强类型)的更多相关文章
- .net core 基于NPOI 的excel导入导出类,支持自定义导出哪些字段,和判断导入是否有失败的记录
#region 从Excel导入 //用法 //var cellHeader = new Dictionary<string, string>(); //cellHeader.Add(&q ...
- 基于NPOI的Excel导入导出类库
概述 支持多sheet导入导出.导出字段过滤.特性配置导入验证,非空验证,唯一验证,错误标注等 用于基础配置和普通报表的导入导出,对于复杂需求,比如合并列,公式,导出图片等暂不支持 GitHub地址: ...
- NPOI实现Excel导入导出
NPOI实现Excel的导入导出,踩坑若干. Cyan是博主[Soar360]自2014年以来开始编写整理的工具组件,用于解决现实工作中常用且与业务逻辑无关的问题. 什么是NPOI? NPOI 是 P ...
- NPOI读写Excel组件封装Excel导入导出组件
后台管理系统多数情况会与Excel打交道,常见的就是Excel的导入导出,对于Excel的操作往往是繁琐且容易出错的,对于后台系统的导入导出交互过程往往是固定的,对于这部分操作,我们可以抽离出公共组件 ...
- 基于POI的Excel导入导出(JAVA实现)
今天做了个excel的导入导出功能,在这记录下. 首先现在相关poi的相关jar包,资源链接:http://download.csdn.net/detail/opening_world/9663247 ...
- vue中excel导入导出组件
vue中导入导出excel,并根据后台返回类型进行判断,导入到数据库中 功能:实现js导入导出excel,并且对导入的excel进行展示,当excel标题名称和数据库的名称标题匹配时,则对应列导入的数 ...
- 基于NPOI的Execl导入导出例子
源地址:http://bbs.csdn.net/topics/390830774
- Excel导入导出组件的设计
前言: 距离一篇文章,又八九个月过去了,随着在园子露脸的次数越来越少,正如我们淡忘上一波大神那样,我们也正下一波所淡忘. 这八九个月,前前后,游走在十来个项目上,忙,却找不到成就感. 人过30后,也是 ...
- java简易excel导入导出工具(封装POI)
Octopus 如何导入excel 如何导出excel github项目地址 Octopus Octopus 是一个简单的java excel导入导出工具. 如何导入excel 下面是一个excel文 ...
随机推荐
- SecureCRT按退格键出现^H问题解决
解决办法一: 解决办法二: ctrl+backspace.即是返回
- 关于jqueryUI里的拖拽排序
主要参考http://jsfiddle.net/KyleMit/Geupm/2/ (这个站需要FQ才能看到效果) 其实是jqueryUI官方购物车拖拽添加例子的增强版,就是在拖拽的时候增加了排序 这 ...
- SpringNote01.基于SpringMVC-Hibernate的Blog系统
最近,在学习Spring,做这样一个简单的blog系统,主要是让自己动手练习使用Spring,熟练的使用才干进一步的深入学习.该项目使用Maven构建,使用git进行代码管理,通过这样一个小项目,熟悉 ...
- 【机房系统知识小结点系列】之遍历窗体中的控件,判断Text是否为空?
做机房系统时,几乎每个窗体中都会用到判断界面中的控件是否为空的情景.我们曾经是这样走来的: 第一版: 好处:对窗体界面中的Text等控件,逐一做判断,当用户输入某一项为空的时候,会议弹出框的形式,告诉 ...
- WorkFlow WF如何为一个集合赋值
今天刚刚开始学习WorkFlow.无奈WF网络上的学习资料实在太少. 刚刚学到Foreach控制流的使用,需要一个集合参数.经研究,静态赋值可以搞定.动态赋值还没. 首先添加一个List<int ...
- 《think in python》学习-10
think in python 10 列表 和字符串相似,列表是值得序列.在列表中,它可以是任何类型,列表中的值成为元素,有时也称为列表项 s = [10,20,30,40] print s #列表也 ...
- android代码集锦
调用root权限的应用: /** * 执行Command命令的函数 * * @param command 命令 * @return 执行结果 */ public static boolean runR ...
- hdu1281棋盘游戏
Problem Description 小希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的“车”,并且使得他们不能互相攻击,这当然很简单,但是Gardon限制了只 ...
- jquery ajax后台向前台传list 前台用jquery $.each遍历list
$.ajax({ type: 'post', url: xxx.action', dataType: 'text', success: function(data){ var dataObj=eval ...
- PS切图保存后的背景图为透明
1.若想PS切图保存后的背景图为透明,那么则需要在如下图中所示的修改即可,切图后[文件]——>[存储为web格式]——>[PNG-24]: 2.要想在css中的背景图片为相通,则先剪切一个 ...