如何设计通用的csvjsonlistdatatable导出为excel的功能模块?

2026-05-22 18:221阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1543个文字,预计阅读时间需要7分钟。

导出Excel的场景,我一般都直接将List直接导出成一张sheet,使用Npoi.Mapper库非常方便。最近我经常需要将接口返回的jsonArray转换成一张excel表,例如从Elasticsearch或Clickhouse中获取的列是不固定的。

导出excel的场景我一般都是一个List直接导出成一张sheet,用Npoi.Mapper库很方便,最近我经常是需要将接口返回的jsonarray转成一张excel表,比如从elasticsearch中或者从clickhouse中拿到的列是不固定的,比如从clickhouse中是根据select语句中的字段集合变化而变化,无法提前定义一个未知class再反序列化!所以我想了另外一种办法,也就是本文要分享的:动态生成class+模板引擎的方式来生成Excel/Word/Html/PDF等

代码我已放到github上

github.com/yuzd/Exporter

欢迎star!

整体思路是:
  • 1如果无法预先定义class那就根据input来动态生成class类T
  • 2再把数据装载到List集合中
  • 3利用模板引擎+List生成目标文件
第一步:先根据input来动态生成class类T

根据目前需要,input分成2大类

1. 无法确定class类型的
  • CSV格式逗号分隔的string集合
  • jsonarray字符串
  • DataTable
  • DataSet
  • DataReader

针对这种场景,那么我们需要按流程一步步来先动态生成class类

2. 已经知道class类型的
  • List集合(T即为我们想要的class类型)
  • key,value形式的Map集合(key集合作为列,value的类型即为我们想要的class类型)

针对这种场景,那么在流程中我们只需要最后一步利用模板引擎即可

动态生成class类的文本 1. csv的场景

csv文件本身双击可以打开,csv文件比如你发到qq或者微信,预览不了,转成excel的话可以直接预览

vararrCSV=newList<string>(); arrCSV.Add("Name,Age,测试"); arrCSV.Add("1112,20,hello"); arrCSV.Add("1232,21,world");

先根据第一列"Name,Age,测试"采用Razor模板引擎生成一个class的文本

usingSystem; publicclass@Model.ClassName{ //constructor public@Model.ClassName( @foreach(varpropinModel.Properties){ <text>string@prop,</text> } //addafakeproperty stringfake=null) { @foreach(varpropinModel.Properties){ <text>this.@prop=@prop;</text> } }//endconstructor //properties @foreach(varpropinModel.Properties){ <text>publicstring@prop{get;set;}</text> } }//endclass

生成的class文本是长这样的:

image
  • 为了后续确保字段相同的都共用一个class类型,class的名称默认的生成规则是Data_${字段拼接string}的hash
2. jsonarray的场景

stringjson=@"[ {'Name':'AndreiIgnat', 'WebSite':'xxxx/', 'CV':'adada.xls' }, {'Name':'YourName', 'WebSite':'yourwebsite', 'CV':'cv.doc' } ]"; vardata2=ExportFactory.ExportDataJson(json,ExportToFormat.Excel); File.WriteAllBytes("a.xlsx",data2);

采用Xamasoft.JsonClassGenerator库生成class文本

publicclassData1888056300 { publicstringName{get;set;} publicstringWebSite{get;set;} publicstringCV{get;set;} } 3. DataTable等其他的场景

比如DataTable,先从里面取所有的列,然后按照和1同样的方式即可生成class文本

动态编译生成class类

按照上面的方式生成了class的类文本,接下来需要动态编译成class类并加载到当前的Domain中。

采用natasha组件,用法如下

AssemblyCSharpBuilderbuilder=new("ExportCoreClass") { Domain=DomainManagement.Default }; //code=class文本 builder.Add(code); varasm=builder.GetAssembly(); //这个type就是我们想要的class类型 vartype=asm.DefinedTypes.First(t=>t.Name==mrj.ClassName);

这里要注意一点,因为className我们是特定规则生成的,所以在动态编译生成class之前先检查当前Domain 中是否已存在

///<summary> ///检测当前domain已经创建好了相同的class ///</summary> ///<paramname="className"></param> ///<returns></returns> privatestaticType?GetExistedTypeInCurrentDomain(stringclassName) { try { //检测当前domain已经创建好了相同的class vartypeExisting=AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a=>a.GetTypes()) .FirstOrDefault(t=>t.FullName!=null&&t.FullName.Equals(className)); if(typeExisting!=null) returntypeExisting; } catch(Exception) { //ignore } returnnull; } 第二步:数据装载到List集合中

这一步比较简单,因为class类型已经生成好了,接下来就是采用反射的方式,创建一个List集合, 在把input数据的每一项根据反射生成T的实例装载进去就好了

image
第二步:利用模板引擎+List生成目标文件

其实上面已经用了Razor模板引擎来帮我们生成class类文本了,Razor模板引擎非常强大,扩展性也非常好

这里我们采用不同的类型对应不同的Razor模板,目前已经实现了的有:

  • Excel2003
  • Excel2007及以上
  • Word2003
  • Word2007及以上
  • Html(Table)

如下图:

image

采用工厂模式暴露对外使用,不同的output采用不同的类进行处理,也方便日后新增其他类型的导出(比如PDF)

以Excel为例子

非POI库的方式来生成excel,先介绍下excel的模板是什么样子

<= 2003版本之前的excel是这样的结构:

image

= 2007版本的excel是这样的结构:

<?xmlversion='1.0'encoding='UTF-8'standalone='yes'?> <worksheetxmlns='schemas.openxmlformats.org/spreadsheetml/2006/main'xmlns:r='schemas.openxmlformats.org/officeDocument/2006/relationships'> <sheetData> @Include(Model.NameOfT+"Excel2007Header") @foreach(variteminModel.Data){ @Include(Model.NameOfT+"Excel2007Item",item) } </sheetData> </worksheet>

根据上面的xml结构还需要用DocumentFormat.OpenXml库来生成excel

///<summary> ///生成excel字节数组 ///</summary> ///<paramname="worksheetName"></param> ///<paramname="textSheet"></param> ///<returns></returns> privatebyte[]CreateExcel2007(string[]worksheetName,string[]textSheet) { usingvarms=newMemoryStream(); usingvarsd=SpreadsheetDocument.Create(ms,SpreadsheetDocumentType.Workbook); varworkbook=sd.AddWorkbookPart(); varstrSheets="<sheets>"; for(vari=0;i<worksheetName.Length;i++) { varsheet=workbook.AddNewPart<WorksheetPart>(); WriteToPart(sheet,textSheet[i]); strSheets+=string.Format("<sheetname=\"{1}\"sheetId=\"{2}\"r:id=\"{0}\"/>", workbook.GetIdOfPart(sheet),worksheetName[i],(i+1)); } strSheets+="</sheets>"; WriteToPart(workbook,string.Format( "<?xmlversion=\"1.0\"encoding=\"UTF-8\"standalone=\"yes\"?><workbookxmlns=\"schemas.openxmlformats.org/spreadsheetml/2006/main\"xmlns:r=\"schemas.openxmlformats.org/officeDocument/2006/relationships\">{0}</workbook>", strSheets )); sd.Close(); returnms.ToArray(); }

其他类型的output也是类似套路,制定模板,然后List数据+模板+加工=最终文件

nuget地址以及常用的使用方法

Install-Package ExporterCore

csv(逗号分隔)导出为excel

vararrCSV=newList<string>(); arrCSV.Add("Name,WebSite,连接"); arrCSV.Add("111,msprogrammer.serviciipeweb.ro/,serviciipeweb.ro/iafblog/content/binary/cv.doc"); arrCSV.Add("123,msprogrammer.serviciipeweb.ro/,serviciipeweb.ro/iafblog/content/binary/cv.doc"); vardata=ExportFactory.ExportDataCsv(arrCSV.ToArray(),ExportToFormat.Excel2007); File.WriteAllBytes("a.xlsx",data); json导出为excel

stringjson=@"[ {'Name':'AndreiIgnat', 'WebSite':'xxx/', 'CV':'aaaaa/binary/cv.doc' }, {'Name':'YourName', 'WebSite':'yourwebsite', 'CV':'cv.doc' } ]"; vardata2=ExportFactory.ExportDataJson(json,ExportToFormat.Excel); File.WriteAllBytes("a.xlsx",data2); list导出为excel

List<Person>listWithPerson=newList<Person> { newPerson { Name="aa", Aget=12 }, newPerson { Name="dasda", Aget=1222 } }; vardata=ExportFactory.ExportData(listWithPerson,ExportToFormat.Excel); File.WriteAllBytes("a.xlsx",data); 多个list导出同个excel的多张Sheet

varp=newPerson{Name="andrei",WebSite="xxx.ro/",CV="daary/cv.doc"}; varp1=newPerson{Name="you",WebSite="yourwebsite.com/"}; varlist=newList<Person>(){p,p1}; varkvp=newList<Tuple<string,string>>(); for(inti=0;i<10;i++) { varq=newTuple<string,string>("Thisiskey"+i,"Value"+i); kvp.Add(q); } varexport=newExportExcel2007<Person>(); vardata=export.ExportMultipleSheets(newIList[]{list,kvp}); File.WriteAllBytes("multiple.xlsx",data); 未完待续

后续可能会完善一下内置的模板可以让使用者定制化,这样就完整了

关注公众号一起学习


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。

本文共计1543个文字,预计阅读时间需要7分钟。

导出Excel的场景,我一般都直接将List直接导出成一张sheet,使用Npoi.Mapper库非常方便。最近我经常需要将接口返回的jsonArray转换成一张excel表,例如从Elasticsearch或Clickhouse中获取的列是不固定的。

导出excel的场景我一般都是一个List直接导出成一张sheet,用Npoi.Mapper库很方便,最近我经常是需要将接口返回的jsonarray转成一张excel表,比如从elasticsearch中或者从clickhouse中拿到的列是不固定的,比如从clickhouse中是根据select语句中的字段集合变化而变化,无法提前定义一个未知class再反序列化!所以我想了另外一种办法,也就是本文要分享的:动态生成class+模板引擎的方式来生成Excel/Word/Html/PDF等

代码我已放到github上

github.com/yuzd/Exporter

欢迎star!

整体思路是:
  • 1如果无法预先定义class那就根据input来动态生成class类T
  • 2再把数据装载到List集合中
  • 3利用模板引擎+List生成目标文件
第一步:先根据input来动态生成class类T

根据目前需要,input分成2大类

1. 无法确定class类型的
  • CSV格式逗号分隔的string集合
  • jsonarray字符串
  • DataTable
  • DataSet
  • DataReader

针对这种场景,那么我们需要按流程一步步来先动态生成class类

2. 已经知道class类型的
  • List集合(T即为我们想要的class类型)
  • key,value形式的Map集合(key集合作为列,value的类型即为我们想要的class类型)

针对这种场景,那么在流程中我们只需要最后一步利用模板引擎即可

动态生成class类的文本 1. csv的场景

csv文件本身双击可以打开,csv文件比如你发到qq或者微信,预览不了,转成excel的话可以直接预览

vararrCSV=newList<string>(); arrCSV.Add("Name,Age,测试"); arrCSV.Add("1112,20,hello"); arrCSV.Add("1232,21,world");

先根据第一列"Name,Age,测试"采用Razor模板引擎生成一个class的文本

usingSystem; publicclass@Model.ClassName{ //constructor public@Model.ClassName( @foreach(varpropinModel.Properties){ <text>string@prop,</text> } //addafakeproperty stringfake=null) { @foreach(varpropinModel.Properties){ <text>this.@prop=@prop;</text> } }//endconstructor //properties @foreach(varpropinModel.Properties){ <text>publicstring@prop{get;set;}</text> } }//endclass

生成的class文本是长这样的:

image
  • 为了后续确保字段相同的都共用一个class类型,class的名称默认的生成规则是Data_${字段拼接string}的hash
2. jsonarray的场景

stringjson=@"[ {'Name':'AndreiIgnat', 'WebSite':'xxxx/', 'CV':'adada.xls' }, {'Name':'YourName', 'WebSite':'yourwebsite', 'CV':'cv.doc' } ]"; vardata2=ExportFactory.ExportDataJson(json,ExportToFormat.Excel); File.WriteAllBytes("a.xlsx",data2);

采用Xamasoft.JsonClassGenerator库生成class文本

publicclassData1888056300 { publicstringName{get;set;} publicstringWebSite{get;set;} publicstringCV{get;set;} } 3. DataTable等其他的场景

比如DataTable,先从里面取所有的列,然后按照和1同样的方式即可生成class文本

动态编译生成class类

按照上面的方式生成了class的类文本,接下来需要动态编译成class类并加载到当前的Domain中。

采用natasha组件,用法如下

AssemblyCSharpBuilderbuilder=new("ExportCoreClass") { Domain=DomainManagement.Default }; //code=class文本 builder.Add(code); varasm=builder.GetAssembly(); //这个type就是我们想要的class类型 vartype=asm.DefinedTypes.First(t=>t.Name==mrj.ClassName);

这里要注意一点,因为className我们是特定规则生成的,所以在动态编译生成class之前先检查当前Domain 中是否已存在

///<summary> ///检测当前domain已经创建好了相同的class ///</summary> ///<paramname="className"></param> ///<returns></returns> privatestaticType?GetExistedTypeInCurrentDomain(stringclassName) { try { //检测当前domain已经创建好了相同的class vartypeExisting=AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a=>a.GetTypes()) .FirstOrDefault(t=>t.FullName!=null&&t.FullName.Equals(className)); if(typeExisting!=null) returntypeExisting; } catch(Exception) { //ignore } returnnull; } 第二步:数据装载到List集合中

这一步比较简单,因为class类型已经生成好了,接下来就是采用反射的方式,创建一个List集合, 在把input数据的每一项根据反射生成T的实例装载进去就好了

image
第二步:利用模板引擎+List生成目标文件

其实上面已经用了Razor模板引擎来帮我们生成class类文本了,Razor模板引擎非常强大,扩展性也非常好

这里我们采用不同的类型对应不同的Razor模板,目前已经实现了的有:

  • Excel2003
  • Excel2007及以上
  • Word2003
  • Word2007及以上
  • Html(Table)

如下图:

image

采用工厂模式暴露对外使用,不同的output采用不同的类进行处理,也方便日后新增其他类型的导出(比如PDF)

以Excel为例子

非POI库的方式来生成excel,先介绍下excel的模板是什么样子

<= 2003版本之前的excel是这样的结构:

image

= 2007版本的excel是这样的结构:

<?xmlversion='1.0'encoding='UTF-8'standalone='yes'?> <worksheetxmlns='schemas.openxmlformats.org/spreadsheetml/2006/main'xmlns:r='schemas.openxmlformats.org/officeDocument/2006/relationships'> <sheetData> @Include(Model.NameOfT+"Excel2007Header") @foreach(variteminModel.Data){ @Include(Model.NameOfT+"Excel2007Item",item) } </sheetData> </worksheet>

根据上面的xml结构还需要用DocumentFormat.OpenXml库来生成excel

///<summary> ///生成excel字节数组 ///</summary> ///<paramname="worksheetName"></param> ///<paramname="textSheet"></param> ///<returns></returns> privatebyte[]CreateExcel2007(string[]worksheetName,string[]textSheet) { usingvarms=newMemoryStream(); usingvarsd=SpreadsheetDocument.Create(ms,SpreadsheetDocumentType.Workbook); varworkbook=sd.AddWorkbookPart(); varstrSheets="<sheets>"; for(vari=0;i<worksheetName.Length;i++) { varsheet=workbook.AddNewPart<WorksheetPart>(); WriteToPart(sheet,textSheet[i]); strSheets+=string.Format("<sheetname=\"{1}\"sheetId=\"{2}\"r:id=\"{0}\"/>", workbook.GetIdOfPart(sheet),worksheetName[i],(i+1)); } strSheets+="</sheets>"; WriteToPart(workbook,string.Format( "<?xmlversion=\"1.0\"encoding=\"UTF-8\"standalone=\"yes\"?><workbookxmlns=\"schemas.openxmlformats.org/spreadsheetml/2006/main\"xmlns:r=\"schemas.openxmlformats.org/officeDocument/2006/relationships\">{0}</workbook>", strSheets )); sd.Close(); returnms.ToArray(); }

其他类型的output也是类似套路,制定模板,然后List数据+模板+加工=最终文件

nuget地址以及常用的使用方法

Install-Package ExporterCore

csv(逗号分隔)导出为excel

vararrCSV=newList<string>(); arrCSV.Add("Name,WebSite,连接"); arrCSV.Add("111,msprogrammer.serviciipeweb.ro/,serviciipeweb.ro/iafblog/content/binary/cv.doc"); arrCSV.Add("123,msprogrammer.serviciipeweb.ro/,serviciipeweb.ro/iafblog/content/binary/cv.doc"); vardata=ExportFactory.ExportDataCsv(arrCSV.ToArray(),ExportToFormat.Excel2007); File.WriteAllBytes("a.xlsx",data); json导出为excel

stringjson=@"[ {'Name':'AndreiIgnat', 'WebSite':'xxx/', 'CV':'aaaaa/binary/cv.doc' }, {'Name':'YourName', 'WebSite':'yourwebsite', 'CV':'cv.doc' } ]"; vardata2=ExportFactory.ExportDataJson(json,ExportToFormat.Excel); File.WriteAllBytes("a.xlsx",data2); list导出为excel

List<Person>listWithPerson=newList<Person> { newPerson { Name="aa", Aget=12 }, newPerson { Name="dasda", Aget=1222 } }; vardata=ExportFactory.ExportData(listWithPerson,ExportToFormat.Excel); File.WriteAllBytes("a.xlsx",data); 多个list导出同个excel的多张Sheet

varp=newPerson{Name="andrei",WebSite="xxx.ro/",CV="daary/cv.doc"}; varp1=newPerson{Name="you",WebSite="yourwebsite.com/"}; varlist=newList<Person>(){p,p1}; varkvp=newList<Tuple<string,string>>(); for(inti=0;i<10;i++) { varq=newTuple<string,string>("Thisiskey"+i,"Value"+i); kvp.Add(q); } varexport=newExportExcel2007<Person>(); vardata=export.ExportMultipleSheets(newIList[]{list,kvp}); File.WriteAllBytes("multiple.xlsx",data); 未完待续

后续可能会完善一下内置的模板可以让使用者定制化,这样就完整了

关注公众号一起学习


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。