自从ms office 2007开始.就开始支持open xml.而且发布了一套api.还有个对应的 open xml tool,我看了一下,觉得不值得花这个力气去熟悉一套非标准的api,在网上查资料的时候,我知道了原来docx的原来就是一个标准zip包,试着解压来看了看,文件结构比较简单,如果忽略掉文件之样的关联引用.完成可以自己生成xml,然后再打包成docx.为什么不呢,这样应该相当的简单.当然中间我也走了一点弯路,比如可恶的zippackage.好了.让我们开始吧.
step 1:当然是我们的controller.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using CJRApp2.Data;
using System.IO;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Checksums;
namespace CJRApp2.Web.Controllers
{
public class CreatedocController : Controller
{
/// <summary>
/// uri调用格式可能为Createdoc?templateName=simple&arg=1&key=value
/// </summary>
/// <param name="templateName"></param>
/// <returns></returns>
public ActionResult Index(string templateName)
{
//WebFormView
//我们的约定是模板应该生成两个ViewData
//入口为模板名,参数设置应该模板文件自己去分析uri
//docx用于存放文件内容
//docName用于存放下载后的文件名
this.transView("~/Views/Createdoc/" + templateName + ".aspx");
byte[] bytes = this.ViewData["docx"] as byte[];
return this.File(bytes
, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
, this.ViewData["docName"] + ".docx");
}
/// <summary>
/// 由视图产生输出字符串,docx中的xml默认是utf-8无签名的.如果改用.aspx视图动态输出记得另存为有签名的
/// </summary>
/// <param name="viewName">视图文件名</param>
/// <returns>视图结果</returns>
private string transView(string viewName)
{
ViewEngineResult ver = ViewEngines.Engines.FindView(this.ControllerContext, viewName, null);
StringWriter sw = new StringWriter();
ViewContext vc = new ViewContext(this.ControllerContext, ver.View, this.ViewData, this.TempData, sw);
ver.View.Render(vc, sw);
ver.ViewEngine.ReleaseView(this.ControllerContext, ver.View);
sw.Flush();
string returnValue = sw.GetStringBuilder().ToString();
sw.Close();
sw.Dispose();
return returnValue;
}
/// <summary>
/// 转换视图结果进压缩包
/// </summary>
/// <param name="zs">压缩文件输出流</param>
/// <param name="uri">压缩文件uri</param>
/// <param name="viewName">视图文件名,形如~/Views/ctrollername/templatename/word/document.aspx</param>
/// <returns></returns>
public void transView2zip(ZipOutputStream zs, string uri, string viewName)
{
string fileContent=this.transView(viewName);
ZipEntry entry = new ZipEntry(uri);
zs.PutNextEntry(entry);
byte[] bytes = Encoding.UTF8.GetBytes(fileContent);
zs.Write(bytes, 0, bytes.Length);
}
/// <summary>
/// 添加源文件到压缩包
/// </summary>
/// <param name="zs">压缩文件输出流</param>
/// <param name="uri">压缩文件uri</param>
/// <param name="file">本地文件全</param>
public void AddFile2zip(ZipOutputStream zs,string uri,string file) {
FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
ZipEntry entry = new ZipEntry(uri);
zs.PutNextEntry(entry);
fs.CopyTo(zs);
fs.Flush();
fs.Close();
fs.Dispose();
}
}
}
step 2:现在我们希望有一个模板文件template.aspx提供zip包存放在ViewData["docx"]中就可以提供服务了,它应该就是一个根据需要生成zip包的过程,我们先创建一个simple.aspx的视图文件,建立一个simple的对应目录存放解压后的docx,可能的结构如图所示
现在开始我们的simple.aspx吧.
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<%@ import namespace="CJRApp2.Web.Controllers" %>
<%@ import namespace="System.IO" %>
<%@ import namespace="ICSharpCode.SharpZipLib.Zip" %>
<%
//文件名应该为~/......xxx.aspx?
CreatedocController ctrl = ViewContext.Controller as CreatedocController;
HttpContextBase http = ViewContext.HttpContext;
string viewpath = (this.ViewContext.View as WebFormView).ViewPath;
string filePath = viewpath.Substring(0, viewpath.Length - 5) + "\\";
filePath = Server.MapPath(filePath);
// String[] files= Directory.GetFileSystemEntries(filePath);
IEnumerable<string> files= Directory.EnumerateFiles(filePath, "*", SearchOption.AllDirectories);
using (MemoryStream ms = new MemoryStream())
{
//不要考虑ZipPackage,微软自己都说它不是一个标准的zip,试试SharpZip
using (ZipOutputStream zs = new ZipOutputStream(ms))
{
zs.UseZip64 = UseZip64.Off;
//非aspx直接进压缩包
foreach (string file in files)
{
string uri = file.Substring(filePath.Length);
if (!uri.EndsWith(".aspx")){
ctrl.AddFile2zip(zs, uri, file);
}
}
//现在我们修改了word/document.xml=>word/document.aspx
//事实上可以用同一个aspx生成不同的xml然后进压缩包,比如类似的chart
//实现在里面document.aspx
ctrl.transView2zip(zs, "word/document.xml",
"~/views/createdoc/simple/word/document.aspx");
}
//设置ctrl.ViewData 以返回数据,现在所有的生成过程都是在viewpage中完成的
//至于controller 会读取 ViewData,然后以docName指定的名字返回压缩文件,也就是docx
ctrl.ViewData["docx"] = ms.ToArray();
ctrl.ViewData["docName"] = "下载后的文件名为simple";
}
%>
step3:很好,它开始工作了.但这不是我们所需要的.我们的工作应该是document.aspx,因为我们需要它来动态的生成word/document.xml,尝试重命word\documents.xml为
aspx,simple能根据我们定义的规则输出视图并添加入zip包,这很轻松,不是吗,当然有点小小的友情提供.docx解压出来的xml文件是utf-8(无签名)格式的,需要另存为有签名的,为了方便我们修改document.aspx.我们可能需要格式化xml.记得不要在xml文档声明之前添加回车.换行.可能会导致文档的开始部分有点点丑.它的第一行可能是这样的
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
我们可以试着加入我们的逻辑在这个document.aspx中.比如做为示例.我添加了一点点内容
<%foreach (string key in Request.QueryString.AllKeys) { %>
<w:p w:rsidR="003047AC" w:rsidRDefault="003047AC">
<w:pPr>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t><%=key + ":" + Request.QueryString[key]%></w:t>
</w:r>
</w:p>
<%} %>
step 4:收工.总结一下,我们需要提供的是一个aspx文件和一个对应的docx解压目录.aspx用于指导怎么生成压缩包.压缩包里的文档就是我们的docx模板.需要动态生成的部分可以完全在里面实现.不需要任何多余的东西.再添加一个模板将会是一件很轻松的事,而且重要的是你不再需要重新生成你的工程项目.