C#中word导出功能骚操作
马上过牛年了,先祝大家新年好,身体好,心情好!!!
年前最后写一篇之前项目开发的一个功能,自己根据系统业务,想到的一个解决办法,效率还是不错的,废话不多说,开整!!!
需求:企业填报自己的企业信息到系统中,最后需要将数据以给定word模板形式导出,功能简单,就是要开发快,赶及
分析:主要费时间的工作是设计企业填报表单设计实现,以及根据提供的word模板导出数据这块儿,因为涉及到的表单比较多,一个表单最少也有差不多150多个字段,一个一个对,头发也得一把一把的掉
想到的解决法案:在导出word这个功能模块儿,写一些通用的方法,减少一些工作量。
word数据导出功能,思路就是在word模板中每一个需要填数据的地方命名一个标签,代码中找到对应命名的标签,插入数值
传统做法,第一步:在word模板中填写标签 第二步:程序中每个插入字段数据和word模板标签对应,最后插值,这样做有一个问题就是比较耗时间,而且很容易出错
我的做法,第一步:给数据字段一个自定义特性,在自定定义特性中写上对应的标签地址,应用反射的方法将数据最终插入到word模板中。这样就省去了第一步在word中写标签这样繁杂的操作。这样做的问题就是性能比较差,但是可以忽略不计
大体思路就这样,我就单独写一个demo供大家参考,之后能用就用,重在思路和想法的分享和讨论
开写:
新建一个项目:exportwordmodel
最终项目简易结构:
将没用的东西全部去掉,修改index.cshtml页面成这样:
1 @{ 2 viewbag.title = "home page"; 3 } 4 <div class="jumbotron" style="text-align: center"> 5 @*<h1>asp.net</h1>*@ 6 <input type="button" value="导出" onclick="location.href = '@url.action("getexport","home")'" /> 7 </div>
在 homecontroller 中创建:getexport
创建一个类exportfileoperator(所有的word操作),此类需要继承controller,因为有返回file操作方法
1、 在getexport中首先命名一个导出word标题就叫:测试导出word文件
string title = "测试导出word文件";
创建doc:
var doc = exportfileoperator.createbuilder("groupform.doc");
2、createbuilder方法实现为(此处操作需要aspose.word组件,是操作word的,这个需要大家自己去找一下,或者网上找个破解的):
1 private static string _temppath = appdomain.currentdomain.basedirectory; 2 public static (document doc, documentbuilder builder) createbuilder(string tempfilename) 3 { 4 string temppath = $"{_temppath}{tempfilename}"; 5 document doc = new document(temppath); 6 return (doc, new documentbuilder(doc)); 7 }
3、插入标题(需要在word中写一个标签,作为标题插入的地址):
最终可以显示结果为这样:
方法:
exportfileoperator.inserttitle(ref doc.item2, title);//插入标题
public static void inserttitle(ref documentbuilder builder, string filename, string tempbookmarkname = "title") { builder.movetobookmark(tempbookmarkname); builder.write(filename); }
4、根据业务实体,将实体数据写入到word中,也是核心所在
首先命名一个数据类:
public class enterpriseentity { #region 实体成员 /// <summary> /// id /// </summary> public string id { get; set; } /// <summary> /// 团队名 /// </summary> [description("企业名称")] public string v1 { get; set; } /// <summary> /// 统一社会信用代码 /// </summary> [description("统一社会信用代码")] public string v3 { get; set; } /// <summary> /// 成立日期 /// </summary> [description("成立日期")] public string v4 { get; set; } /// <summary> /// 参赛行业领域 /// </summary> [description("参赛行业领域")] public string v5 { get; set; } /// <summary> /// 行政区域 /// </summary> [description("行政区域")] public string v6 { get; set; } /// <summary> /// 是否属于国家高新区内的企业 /// </summary> [description("属于国家高新区内的企业")] public string v7 { get; set; } /// <summary> /// 是否属于*经济开发区内的企业 /// </summary> [description("属于*经济开发区内的企业")] public string v9 { get; set; } /// <summary> /// 是否属于*科技企业孵化器内的企业 /// </summary> [description("属于*科技企业孵化器内的企业")] public string v11 { get; set; } /// <summary> /// 是否属于国家大学科技园内的企业 /// </summary> [description("属于国家大学")] public string v13 { get; set; } /// <summary> /// 是否国家备案的众创空间内的企业 /// </summary> [description("国家备案的众创空间内的企业")] public string v20 { get; set; } /// <summary> /// 企业注册类型 /// </summary> [description("企业注册类型")] public string v22 { get; set; } /// <summary> /// 注册资本 /// </summary> [description("注册资本")] public string v24 { get; set; } /// <summary> /// 实收资本(万元人民币) /// </summary> [description("实收资本")] public string v25 { get; set; } /// <summary> /// 企业注册地址 /// </summary> [description("企业注册地址")] public string v26 { get; set; } /// <summary> /// 邮政编码 /// </summary> [description("注册地邮政编码")] public string v27 { get; set; } /// <summary> /// 通信地址 /// </summary> [description("通信地址")] public string v28 { get; set; } /// <summary> /// 邮政编码 /// </summary> [description("通讯地邮政编码")] public string v29 { get; set; } /// <summary> /// 企业网址 /// </summary> [description("企业网址")] public string v30 { get; set; } /// <summary> /// 企业官方微信 /// </summary> [description("企业官方微信")] public string v31 { get; set; } /// <summary> /// 职工总数 /// </summary> [description("职工总数")] public string v32 { get; set; } /// <summary> /// 博 士人数 /// </summary> [description("博 士")] public string v33 { get; set; } /// <summary> /// 硕 士人数 /// </summary> [description("硕 士")] public string v34 { get; set; } /// <summary> /// 本 科人数 /// </summary> [description("本 科")] public string v35 { get; set; } /// <summary> /// 大专及以下人数 /// </summary> [description("大专及以下")] public string v36 { get; set; } /// <summary> /// 高级职称人数 /// </summary> [description("高级职称")] public string v37 { get; set; } /// <summary> /// 中级职称人数 /// </summary> [description("中级职称")] public string v38 { get; set; } /// <summary> /// 初级职称人数 /// </summary> [description("初级职称")] public string v39 { get; set; } /// <summary> /// 高级技工人数 /// </summary> [description("高级技工")] public string v40 { get; set; } /// <summary> /// 上市公司控股企业是否 /// </summary> [description("上市公司控股企业")] public string v41 { get; set; } /// <summary> /// 新三板企业是否 /// </summary> [description("新三板企业")] public string v42 { get; set; } /// <summary> /// 高新技术企业是否 /// </summary> [description("高新技术企业")] public string v43 { get; set; } /// <summary> /// 获得时间 /// </summary> [description("获得时间")] public string v44 { get; set; } /// <summary> /// 登记入库的科技型中小企业是否 /// </summary> [description("登记入库的科技型中小企业")] public string v45 { get; set; } /// <summary> /// 企业概要(不超1000字) /// </summary> [description("企业概要")] public string v46 { get; set; } /// <summary> /// 关 键 词 /// </summary> [description("关 键 词")] public string v47 { get; set; } /// <summary> /// 现融资阶段 /// </summary> [description("现融资阶段")] public string v48 { get; set; } /// <summary> /// 参赛项目产品图片 /// </summary> public string v49 { get; set; } /// <summary> /// 参赛项目收入占去年企业营业收入比例 /// </summary> [description("参赛项目收入占去年企业营业收入比例")] public string v50 { get; set; } /// <summary> /// 参赛项目介绍(1000字以内) /// </summary> [description("参赛项目介绍(1000字以内)")] public string v51 { get; set; } /// <summary> /// 当前五大客户 /// </summary> [description("当前五大客户")] public string v52 { get; set; } /// <summary> /// 当前五大供应商 /// </summary> [description("当前五大供应商")] public string v53 { get; set; } /// <summary> /// 国内市场地位排名 /// </summary> [description("国内市场地位排名")] public string v54 { get; set; } /// <summary> /// 商业模式及业务拓展计划 /// </summary> [description("商业模式及业务拓展计划")] public string v56 { get; set; } /// <summary> /// 经营风险与对策 /// </summary> [description("经营风险与对策")] public string v57 { get; set; } /// <summary> /// 企业管理模式 /// </summary> [description("企业管理模式")] public string v58 { get; set; } /// <summary> /// 公司对管理层及关键人员是否已采取激励措施是否 /// </summary> [description("公司对管理层及关键人员是否已采取激励措施")] public string v59 { get; set; } /// <summary> /// 公司是否考虑员工持股问题?是否 /// </summary> [description("公司是否考虑员工持股问题")] public string v60 { get; set; } /// <summary> /// 企业其他技术、产品及服务(1000字以内) /// </summary> [description("企业其他技术、产品及服务(1000字以内)")] public string v61 { get; set; } /// <summary> /// 参赛目的 /// </summary> [description("参赛目的")] public string v62 { get; set; } /// <summary> /// 并购需求 /// </summary> [description("并购需求")] public string v63 { get; set; } /// <summary> /// 申请大赛组织的大企业对接活动是否 /// </summary> [description("申请大赛组织的大企业对接活动")] public string v64 { get; set; } /// <summary> /// 资金使用计划 /// </summary> [description("债权融资资金使用计划")] public string v65 { get; set; } /// <summary> /// 股权融资需求是否 /// </summary> [description("直接从事研发科技人员数")] public string v66 { get; set; } /// <summary> /// 融资金额(万元¥) /// </summary> [description("上年度吸纳高校应届毕业生人数")] public string v67 { get; set; } /// <summary> /// 拟出让股权比例 /// </summary> [description("参赛项目名称")] public string v68 { get; set; } /// <summary> /// 融资时间 /// </summary> [description("产品市场分析及竞争优势")] public string v69 { get; set; } /// <summary> /// 资金使用计划 /// </summary> [description("股权融资资金使用计划")] public string v70 { get; set; } /// <summary> /// 申请大赛推荐投资机构是否 (修改 申请大赛推荐信贷机构) /// </summary> [description("申请大赛推荐信贷机构")] public string v71 { get; set; } /// <summary> /// 申请大赛组织的融资路演是否 (修改 申请大赛推荐投资机构) /// </summary> [description("申请大赛推荐投资机构")] public string v72 { get; set; } /// <summary> /// 申请国家科技成果转化引导基金设立的子基金推荐 (修改 申请大赛组织的融资路演) /// </summary> [description("申请大赛组织的融资路演")] public string v73 { get; set; } #endregion public list<string> getthisdescriptionname() { var result = new list<string>(); gettype().getproperties().tolist().foreach(f => { var descriptionobj = (descriptionattribute[])f.getcustomattributes(typeof(descriptionattribute), false); if (descriptionobj.length > 0 && !string.isnullorwhitespace(descriptionobj[0].description)) { result.add(descriptionobj[0].description); } }); return result; } }
其中重要的地方是:需要给每个字段一个description,这里面的值对应的就是word模板中的名称,如下:
这里因为数据是保密的,我就将一些字段删除了,包括word模板中的一些也删除了,就拿出一部分。
和数据库交互的部分我也没写,就将查出来的数据先命名一个_enterprisestr,最后用newtonsoft转换成实体这样操作了哈:
enterpriseentity enterprise = jsonconvert.deserializeobject<enterpriseentity>(_enterprisestr);
5、将查出来的数据,插入到word中,完成最终的导出:
1 exportfileoperator.insertformdata(enterprise, ref doc.item1);//实体数据插入 2 return new exportfileoperator().fileresult(title, doc.item1);
其中最重要的方法就是insertformdata这个,他的实现如下:
1 public static void insertformdata<t>(t objformdata, ref document document) 2 { 3 nodecollection alltables = document.getchildnodes(nodetype.table, true); 4 list<string> headdescribenamelist = getobjectheaddescription<t>();//获取实体中每个description中的值 5 foreach (table tablefirst in alltables) 6 { 7 for (int headindex = 0; headindex < headdescribenamelist.count; headindex++)//循环实体中的每个describename 8 { 9 for (int rowindex = 0; rowindex < tablefirst.rows.count; rowindex++)//遍历word模板中所有的table 10 { 11 for (int cellindex = 0; cellindex < tablefirst.rows[rowindex].cells.count; cellindex++)//遍历模板中所有的table每行的列数 12 { 13 if (tablefirst.rows[rowindex].cells[cellindex].gettext() != null && tablefirst.rows[rowindex].cells[cellindex].gettext().contains(headdescribenamelist[headindex]) && 14 ((tablefirst.rows[rowindex].cells.count > cellindex && tablefirst.rows[rowindex].cells[cellindex + 1] != null && tablefirst.rows[rowindex].cells[cellindex + 1].gettext().equals("\a")) || (tablefirst.rows.count > rowindex && tablefirst.rows[rowindex + 1] != null && tablefirst.rows[rowindex + 1].cells[cellindex] != null && tablefirst.rows[rowindex + 1].cells[cellindex].gettext().equals("\a"))))//如果遍历的cell不为空、其中的值能和describename匹配上,并且这个单元的右边的cell或者下边cell有占位,而且是空,就在此处插入值 15 { 16 var objvalue = getobjectvaluebypropname(objformdata, headdescribenamelist[headindex]);//根据describename获取对应的值 17 if (tablefirst.rows[rowindex].cells.count > cellindex && tablefirst.rows[rowindex].cells[cellindex + 1] != null && tablefirst.rows[rowindex].cells[cellindex + 1].gettext().equals("\a")) 18 { 19 insertcell(objvalue, document, tablefirst.rows[rowindex].cells[cellindex + 1]);//优先在右变空位插入值 20 break; 21 } 22 insertcell(objvalue, document, tablefirst.rows[rowindex + 1].cells[cellindex]);//右侧如果没有就在下边空位插入值 23 break; 24 } 25 } 26 } 27 } 28 } 29 }
1 public static list<string> getobjectheaddescription<t>() 2 { 3 var obj = activator.createinstance<t>(); 4 methodinfo method = obj.gettype().getmethod("getthisdescriptionname", new type[] { });//每个实体需要有getthisdescriptionname这个方法 5 return (list<string>)(method?.invoke(obj, null)); 6 }
其中getthisdescriptionname方法需求在每个实体类中有实现:
根据descriptionname获取实体中的值:
1 private static string getobjectvaluebypropname<t>(t objformdata, string descriptionname) 2 { 3 try 4 { 5 var properties = objformdata.gettype().getproperties(); 6 foreach (var propertyinfo in properties) 7 { 8 var descriptionattributes = (descriptionattribute[])propertyinfo.getcustomattributes(typeof(descriptionattribute), false); 9 if (descriptionattributes.length > 0 && !string.isnullorwhitespace(descriptionattributes[0].description) && descriptionattributes[0].description.equals(descriptionname)) 10 { 11 return propertyinfo.getvalue(objformdata) == null ? "无" : propertyinfo.getvalue(objformdata).tostring(); 12 } 13 } 14 return "无"; 15 } 16 catch (exception e) 17 { 18 console.writeline(e); 19 throw; 20 } 21 }
在cell中插入值:
1 private static void insertcell(string value, document doc, cell cell) 2 { 3 cell insertcell = cell; 4 insertcell.firstparagraph.remove(); 5 paragraph p = new paragraph(doc); 6 p.appendchild(new run(doc, (value == null ? "" : value))); 7 p.paragraphformat.alignment = paragraphalignment.center; 8 insertcell.cellformat.verticalalignment = cellverticalalignment.center; 9 insertcell.appendchild(p); 10 }
最后一个方法fileresult:
1 public fileresult fileresult(string filename, document doc) 2 { 3 var filepathname = $"{filename}.doc"; 4 doc.save(path.combine(_temppath, "temp", filepathname), saveformat.doc); //保存word 5 filepathname = path.combine(_temppath, "temp", filepathname); 6 return file(filepathname, "application/doc", $"{filename}.doc"); 7 }
最终效果:
最后说一下,其中有一些细节的地方还是需要做一些处理,暂时没时间写,后期有时间补,先就这样了
大家有什么好的想法或者更好的实现方式,尽管提出来,共同进步
git地址:https://github.com/binzm/exportworkdmodel.git
上一篇: 直接用淡奶油做冰激凌的方法大全,绝对有你喜欢的味道!
下一篇: WPF 基于五点线性平滑曲线算法