Converter Plugin
Convert documents to 24 target formats
The Converter plugin turns Doconut into a document-conversion service. It contributes the engine behind the public DocumentConverter facade and — opt-in — a drop-in widget served by DocImage.axd, so you can convert documents from C#, from the widget, or from a frontend you write yourself.
Install the package
Install-Package Doconut.NETFramework.ConverterKeep the Converter package at the same version as Doconut.NETFramework.
Register the plugin
Every plugin registers the same way: AddPlugin<TPlugin>() in DoconutHost.Initialize.
Doconut.DoconutHost.Initialize(options =>
{
options.LicensePath = HostingEnvironment.MapPath("~/wwwroot/Doconut.Viewer.lic");
options.AddPlugin<Doconut.Plugins.Converter.ConverterPlugin>();
});If no active license grants the
Convertercapability — no license, a legacyTRIALfile, or a license without it —DoconutHost.Initializethrows fromApplication_Start. In System.Web that means every request answers an empty500, and the reason is only in the Windows event log (source ASP.NET). Temporary Demo/NFR licenses are accepted. See License Setup.
Convert from C#
Every conversion returns a seekable MemoryStream positioned at 0. DocumentConverter is stateless: resolve it from DoconutHost.Services wherever you need it and reuse it freely.
// Resolve DocumentConverter from DoconutHost.Services; its constructor is internal, so never `new` it.
Stream pdf = await converter.ConvertAsync("contract.docx", ConversionTarget.Pdf, ct: ct);// 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);Stream html = await converter.WordToHtmlAsync("report.docx", ct);Stream docx = await converter.HtmlToWordAsync(html, ConversionTarget.Docx, ct);Two details that are easy to get wrong: sourceExtension on the stream overload must include the leading dot (".xlsx", not "xlsx"), or it does not resolve. And despite its name, WordToHtmlAsync returns Task<Stream>, not Task<string> — the HTML document (images embedded as Base64) arrives as a stream, like every other result.
In an MVC 5 controller
Make the action asynchronous and await the conversion:
[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");
}In Web Forms: never block on a conversion
A Web Forms page runs on a single-threaded request context. ConvertAsync resumes on that context when it completes, so a page that waits for it synchronously — .Result, .Wait(), .GetAwaiter().GetResult() — holds the very thread the conversion needs to finish. The request never returns. The browser keeps spinning, IIS logs nothing, and the rest of the site keeps working, so it looks like a slow conversion rather than a deadlock.
Mark the page Async="true" and run the conversion as a page async task:
<%@ Page Language="C#" Async="true" CodeBehind="Convert.aspx.cs" Inherits="MyApp.Convert" %>// 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();
}
}));
}Without Async="true" in the @Page directive, RegisterAsyncTask is silently ignored. After an await, use the page's Context; HttpContext.Current can be null there.
Target formats
Pdf, Docx, Doc, Html, Xlsx, Pptx, Png, Jpeg, Csv, Tiff, Bmp, Gif, Svg, Xml,
Txt, Xls, Jp2, Rtf, Odt, Ods, Odp, Epub, Xps, WebpNot every source converts to every target: each source's format family (Word, Excel, PowerPoint, PDF, CAD, Image, Email, Diagram, Project, PSD, web document) has its own set of allowed targets. Don't hardcode this enum as your UI's target list: ?convert=open returns the allowedTargets for the uploaded file, and that is what should drive a picker.
Drop-in widget
The widget's ?convert=open|run|download endpoints are served by DocImage.axd and are off by default. Enable them next to the plugin registration:
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 reads the whole upload before Doconut sees it: keep maxRequestLength (KB) and
// maxAllowedContentLength (bytes) in Web.config above the widget's MaxUploadMb.<div id="doconut-convert"></div>
<script src="/doconut-res/js/doconutConverter.js"></script>
<script>
Doconut.convert('#doconut-convert', { basePath: '/DocImage.axd', maxUploadMb: 25 });
</script>Two things to check when the widget misbehaves:
basePath: '/DocImage.axd'. The widget's default,/doconut, is the ASP.NET Core endpoint. On this package, the endpoints are theDocImage.axdhandler fromWeb.config.- The script and the endpoints are independent. The resource module serves
doconutConverter.jswhether or not the widget is enabled. WithoutAddConverterWidget(), the widget loads, paints its drop zone, and looks entirely correct — and its first action answers404.
Upload limits
ASP.NET reads the whole request body before DocImage.axd runs. An upload above MaxUploadMb but below the host limits gets the widget's own JSON 413 ("File is too large."). An upload above the host limits is rejected by IIS first, with an error page the widget cannot read. Keep both host limits in Web.config above the widget's cap:
<system.web>
<httpRuntime maxRequestLength="30720" /> <!-- KILOBYTES: 30 MB -->
</system.web>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="31457280" /> <!-- BYTES: 30 MB -->
</requestFiltering>
</security>
</system.webServer>Customize the widget
Options passed to Doconut.convert(selector, options):
| Option | Type | Default | Notes |
|---|---|---|---|
basePath | string | /doconut | The endpoint URL. Set '/DocImage.axd' on this package |
resPath | string | /doconut-res | Accepted for consistency with other Doconut widgets; the converter widget does not build any URL from it |
maxUploadMb | number | 25 | Client-side pre-check only; the server enforces its own cap |
licenseUrl | string | null | null | Turns the watermark notice on the result screen into a link |
labels | object | {} | Overrides any subset of the widget's English strings |
Callbacks:
| Callback | Fires when | Payload |
|---|---|---|
onReady() | The widget has rendered its drop screen | — |
onSourceLoaded({ token, pages, sourceExt, allowedTargets }) | ?convert=open succeeds | Source token, page count, source extension (no leading dot), allowed targets |
onConverted({ downloadToken, resultToken, resultPages, downloadName, watermarked, target }) | ?convert=run succeeds | The run response plus the requested target |
onDownload({ downloadName, downloadToken }) | The user clicks Download | Fires alongside the browser's own download |
onError({ phase, message }) | An open or run request fails | phase is 'open' or 'run'; message is the sanitized server error |
Doconut.convert() returns the widget instance: reset() returns to the drop screen, loadFile(file) starts the flow with a File, and destroy() removes it.
Build your own frontend
The widget is a client for this HTTP contract on DocImage.axd:
| Route | Purpose | Success response |
|---|---|---|
POST /DocImage.axd?convert=open (multipart, field file) | Upload and open a source document | 200 — { token, pages, sourceExt, allowedTargets } |
POST /DocImage.axd?token=<token>&convert=run&target=<ext> | Convert the uploaded source to target | 200 — { downloadToken, resultToken, resultPages, downloadName, watermarked } |
GET /DocImage.axd?convert=download&token=<downloadToken> | Stream the converted file | 200 — the file, Content-Disposition: attachment |
Uploaded sources are kept for 30 minutes; after that, run answers 404 and the file must be opened again. downloadToken gets its own 30-minute window when the conversion completes. resultToken is an ordinary viewer token that follows the viewer session's lifetime.
sourceExt in the open response has no leading dot ("docx") — the opposite of the sourceExtension parameter of ConvertAsync.
| Route | Status | When | Body |
|---|---|---|---|
| any | 404 | The widget isn't enabled (AddConverterWidget() was never called) | status only |
| any | 405 | Wrong verb (open/run require POST, download requires GET) | status only |
open | 413 | The upload exceeds MaxUploadMb (but not the IIS limits) | { "error": "File is too large." } |
open | 400 | No multipart body, no file, or a type that cannot be converted | { "error": "..." } |
open | 403 | The file type needs a capability the license does not grant | { "error": "..." } |
run | 400 | A malformed token, an unknown target, or a target not in allowedTargets | { "error": "..." } |
run | 404 | The upload expired or was never opened | { "error": "Upload expired — please re-open the file." } |
open, run | 500 | Processing failed | { "error": "<sanitized message>" } |
download | 400 / 404 | A malformed, unknown, or expired download token | status only |
Resource ownership
The caller owns the MemoryStream a conversion returns and should dispose it after copying or returning it (MVC's File(stream, …) disposes it for you). Do not construct or dispose DocumentConverter yourself — its constructor is internal.
Watermarking
| License state | Startup | Conversion output |
|---|---|---|
Paid viewer license granting Converter | Starts | Clean — watermarked: false |
| Active Temporary/Demo license | Starts | Converts, with the evaluation watermark — watermarked: true |
No license, a legacy TRIAL file, or a license without Converter | Application_Start throws; every request answers an empty 500 | — |
| Expired Temporary/Demo license | Starts | Converts, with the evaluation watermark — watermarked: true |
An integration can be built and tested end to end on an evaluation license before purchase — only the output bytes change.
Troubleshooting
| Symptom | Check |
|---|---|
Every request answers an empty 500 after adding the plugin | The license grants Converter — Windows event log, source ASP.NET |
Resolving DocumentConverter throws | AddPlugin<ConverterPlugin>() is in DoconutHost.Initialize |
| A Web Forms conversion never returns | The page blocks on ConvertAsync — use Async="true" and RegisterAsyncTask |
| Stream conversion says the format is unsupported | sourceExtension includes the leading dot |
The widget loads but its requests return 404 | AddConverterWidget() was not called, or basePath is not '/DocImage.axd' |
| A large upload shows an IIS error page instead of the widget's message | maxRequestLength / maxAllowedContentLength are below MaxUploadMb |
| A target is missing | Use the allowedTargets from convert=open |
| The download expired | Repeat convert=open / convert=run; the tokens are temporary on purpose |
Apakah halaman ini membantu?