转换插件

将文档转换为 24 种目标格式

Converter 插件将 Doconut 转变为文档转换服务。它提供了公共 DocumentConverter 外观背后的引擎,并且(可选)提供由 DocImage.axd 提供的即插即用小部件,因此您可以从 C#、从小部件或从自己编写的前端进行文档转换。

安装包

powershell
Install-Package Doconut.NETFramework.Converter

保持 Converter 包的版本与 Doconut.NETFramework 相同。

注册插件

每个插件的注册方式相同:在 DoconutHost.Initialize 中使用 AddPlugin<TPlugin>()。

csharp
Doconut.DoconutHost.Initialize(options =>
{
    options.LicensePath = HostingEnvironment.MapPath("~/wwwroot/Doconut.Viewer.lic");
    options.AddPlugin<Doconut.Plugins.Converter.ConverterPlugin>();
});

如果没有活动许可证授予 Converter 功能——没有许可证、旧的 TRIAL 文件,或不包含该功能的许可证——DoconutHost.Initialize 会在 Application_Start 抛出异常。在 System.Web 中,这意味着每个请求都会返回空的 500,原因仅记录在 Windows 事件日志(来源 ASP.NET)中。临时 Demo/NFR 许可证是被接受的。参见 许可证设置。

从 C# 转换

每次转换都会返回一个定位在 0 的可搜索 MemoryStream。DocumentConverter 是无状态的:在需要的地方从 DoconutHost.Services 解析它并自由复用。

csharp
// Resolve DocumentConverter from DoconutHost.Services; its constructor is internal, so never `new` it.
Stream pdf = await converter.ConvertAsync("contract.docx", ConversionTarget.Pdf, ct: ct);
csharp
// sourceExtension includes the leading dot. password is null unless the document is protected.
Stream png = await converter.ConvertAsync(upload, ".xlsx", ConversionTarget.Png, password: null, ct: ct);
csharp
Stream html = await converter.WordToHtmlAsync("report.docx", ct);
csharp
Stream docx = await converter.HtmlToWordAsync(html, ConversionTarget.Docx, ct);

两个容易出错的细节:流重载中的 sourceExtension 必须包含前导点(".xlsx",而不是 "xlsx"),否则无法解析。并且尽管名字是 WordToHtmlAsync,它返回的是 Task<Stream>,而不是 Task<string>——HTML 文档(图像以 Base64 形式嵌入)以流的形式返回,就像所有其他结果一样。

在 MVC 5 控制器中

将操作设为异步并 await 转换:

csharp
[HttpPost]
public async Task<ActionResult> ToPdf(HttpPostedFileBase file)
{
    var converter = Doconut.DoconutHost.Services.GetRequiredService<DocumentConverter>();
    Stream pdf = await converter.ConvertAsync(
        file.InputStream, Path.GetExtension(file.FileName), ConversionTarget.Pdf,
        password: null, ct: Response.ClientDisconnectedToken);
    return File(pdf, "application/pdf", Path.GetFileNameWithoutExtension(file.FileName) + ".pdf");
}

在 Web Forms 中:切勿阻塞转换

Web Forms 页面在单线程请求上下文中运行。ConvertAsync 完成后会在该上下文上恢复,因此同步等待(.Result、.Wait()、.GetAwaiter().GetResult())会占用转换所需的线程,导致 请求永不返回。浏览器会一直转圈,IIS 不记录日志,站点其余部分仍然工作,看起来像是转换慢而不是死锁。

将页面标记为 Async="true" 并将转换作为页面异步任务运行:

aspx
<%@ Page Language="C#" Async="true" CodeBehind="Convert.aspx.cs" Inherits="MyApp.Convert" %>
csharp
// Web Forms: the @Page directive needs Async="true", and the conversion runs as a page async task.
// Blocking on it instead (.Result, .Wait(), .GetAwaiter().GetResult()) deadlocks the request:
// it never returns, and nothing is logged.
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack) return;

    RegisterAsyncTask(new PageAsyncTask(async ct =>
    {
        var converter = Doconut.DoconutHost.Services.GetRequiredService<DocumentConverter>();

        using (var pdf = await converter.ConvertAsync(
                   Server.MapPath("~/App_Data/contract.docx"), ConversionTarget.Pdf, ct: ct))
        using (var ms = new MemoryStream())
        {
            if (pdf.CanSeek) pdf.Position = 0;
            await pdf.CopyToAsync(ms);

            Response.ContentType = "application/pdf";
            Response.AddHeader("Content-Disposition", "attachment; filename=contract.pdf");
            Response.BinaryWrite(ms.ToArray());
            // Context, not HttpContext.Current: after an await the latter can be null.
            Context.ApplicationInstance.CompleteRequest();
        }
    }));
}

如果在 @Page 指令中缺少 Async="true",RegisterAsyncTask 将被静默忽略。await 之后请使用页面的 Context,因为此时 HttpContext.Current 可能为 null。

目标格式

text
Pdf, Docx, Doc, Html, Xlsx, Pptx, Png, Jpeg, Csv, Tiff, Bmp, Gif, Svg, Xml,
Txt, Xls, Jp2, Rtf, Odt, Ods, Odp, Epub, Xps, Webp

并非所有源文件都能转换为每种目标格式:每种源文件的格式族(Word、Excel、PowerPoint、PDF、CAD、图像、电子邮件、图表、项目、PSD、网页文档)都有自己允许的目标集合。不要在 UI 中硬编码此枚举作为目标列表:?convert=open 会返回上传文件的 allowedTargets,这才应该用于驱动选择器。

嵌入式小部件

小部件的 ?convert=open|run|download 端点由 DocImage.axd 提供,并且默认 关闭。在插件注册旁边启用它们:

csharp
Doconut.DoconutHost.Initialize(options =>
{
    options.LicensePath = HostingEnvironment.MapPath("~/wwwroot/Doconut.Viewer.lic");
    options.AddPlugin<Doconut.Plugins.Converter.ConverterPlugin>();
    options.AddConverterWidget(widget =>
    {
        widget.MaxUploadMb = 25;
    });
});
// System.Web 在 Doconut 处理之前会读取整个上传内容:请在 Web.config 中将
// maxRequestLength(KB)和 maxAllowedContentLength(字节)设置在小部件的 MaxUploadMb 之上。
html
<div id="doconut-convert"></div>
<script src="/doconut-res/js/doconutConverter.js"></script>
<script>
  Doconut.convert('#doconut-convert', { basePath: '/DocImage.axd', maxUploadMb: 25 });
</script>

当小部件行为异常时,需要检查两件事:

  • basePath: '/DocImage.axd'. 小部件的默认值 /doconut 是 ASP.NET Core 端点。在此包中,端点是来自 Web.config 的 DocImage.axd 处理程序。
  • 脚本和端点相互独立。 资源模块始终提供 doconutConverter.js,无论小部件是否已启用。若未调用 AddConverterWidget(),小部件仍会加载、绘制其拖拽区,并看起来完全正常——但其首次操作会返回 404。

上传限制

ASP.NET 在 DocImage.axd 运行之前会读取整个请求体。上传大小超过 MaxUploadMb 但低于主机限制时,会得到小部件自己的 JSON 413(“文件太大”。)。若上传大小超过主机限制,IIS 会先拒绝请求,并返回一个小部件无法读取的错误页面。请在 Web.config 中同时设置主机限制,以高于小部件的上限:

xml
<system.web>
  <httpRuntime maxRequestLength="30720" />           <!-- KILOBYTES: 30 MB -->
</system.web>
<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="31457280" />  <!-- BYTES: 30 MB -->
    </requestFiltering>
  </security>
</system.webServer>

自定义小部件

传递给 Doconut.convert(selector, options) 的选项:

选项类型默认值备注
basePathstring/doconut端点 URL。此包请设为 '/DocImage.axd'
resPathstring/doconut-res为与其他 Doconut 小部件保持一致而保留;转换小部件不会基于它构建任何 URL
maxUploadMbnumber25仅在客户端进行预检查;服务器会自行强制上限
licenseUrlstring | nullnull将结果页面的水印提示转为链接
labelsobject{}覆盖小部件中任意子集的英文字符串

回调函数:

回调触发时机负载
onReady()小部件已渲染其拖拽界面—
onSourceLoaded({ token, pages, sourceExt, allowedTargets })?convert=open 成功源令牌、页数、源扩展名(不含点)、允许的目标列表
onConverted({ downloadToken, resultToken, resultPages, downloadName, watermarked, target })?convert=run 成功运行响应以及请求的 target
onDownload({ downloadName, downloadToken })用户点击下载与浏览器自身的下载同时触发
onError({ phase, message })打开或运行请求失败phase 为 'open' 或 'run';message 为已清理的服务器错误信息

Doconut.convert() 返回小部件实例:reset() 返回拖拽界面,loadFile(file) 使用 File 开始流程,destroy() 将其移除。

构建您自己的前端

该小部件是对 DocImage.axd 上此 HTTP 合约的客户端:

路由目的成功响应
POST /DocImage.axd?convert=open (multipart, field file)上传并打开源文档200 — { token, pages, sourceExt, allowedTargets }
POST /DocImage.axd?token=<token>&convert=run&target=<ext>将已上传的源转换为 target200 — { downloadToken, resultToken, resultPages, downloadName, watermarked }
GET /DocImage.axd?convert=download&token=<downloadToken>流式传输已转换的文件200 — 文件,Content-Disposition: attachment

已上传的源文件会保存 30 分钟;之后,run 会返回 404,必须重新打开文件。downloadToken 在转换完成后拥有独立的 30 分钟窗口。resultToken 是普通的查看器令牌,其生命周期随查看器会话而定。

open 响应中的 sourceExt 没有前导点(例如 "docx")——这与 ConvertAsync 的 sourceExtension 参数相反。

路由状态何时响应体
any404小部件未启用(从未调用 AddConverterWidget())仅状态码
any405请求动词错误(open/run 需要 POST,download 需要 GET)仅状态码
open413上传超过 MaxUploadMb(但未超过 IIS 限制){ "error": "File is too large." }
open400没有 multipart 正文、没有文件,或文件类型无法转换{ "error": "..." }
open403文件类型需要许可证未授予的功能{ "error": "..." }
run400令牌格式错误、未知的 target,或 target 不在 allowedTargets 中{ "error": "..." }
run404上传已过期或从未打开{ "error": "Upload expired — please re-open the file." }
open, run500处理失败{ "error": "<sanitized message>" }
download400 / 404下载令牌格式错误、未知或已过期仅状态码

资源所有权

调用方拥有转换返回的 MemoryStream,在复制或返回后应自行释放它(MVC 的 File(stream, …) 会为你释放)。不要自行构造或释放 DocumentConverter —— 它的构造函数是内部的。

水印

许可证状态启动时转换输出
授予 Converter 的付费查看器许可证启动干净 — watermarked: false
有效的临时/演示许可证启动转换后带评估水印 — watermarked: true
没有许可证、旧版 TRIAL 文件,或不包含 Converter 的许可证Application_Start 抛异常;每个请求返回空的 500—
已过期的临时/演示许可证启动转换后带评估水印 — watermarked: true

可以在购买前使用评估许可证构建并端到端测试集成——唯一变化的是输出字节。

故障排除

症状检查项
添加插件后每个请求都返回空的 500许可证授予了 Converter —— 检查 Windows 事件日志、ASP.NET 源码
解析 DocumentConverter 抛异常AddPlugin<ConverterPlugin>() 位于 DoconutHost.Initialize 中
Web Forms 转换永不返回页面在 ConvertAsync 上阻塞 —— 使用 Async="true" 并调用 RegisterAsyncTask
流式转换提示格式不受支持sourceExtension 包含前导点
小部件加载但请求返回 404未调用 AddConverterWidget(),或 basePath 不是 '/DocImage.axd'
大文件上传显示 IIS 错误页而非小部件消息maxRequestLength / maxAllowedContentLength 低于 MaxUploadMb
缺少目标使用 convert=open 返回的 allowedTargets
下载已过期重新执行 convert=open / convert=run;令牌本身就是临时的

此页面对您有帮助吗?