Плагин Конвертера

Конвертировать документы в 24 целевых формата

Плагин Converter превращает Doconut в сервис конвертации документов. Он предоставляет движок, стоящий за публичным фасадом DocumentConverter, и — при желании — готовый к использованию виджет, обслуживаемый DocImage.axd, так что вы можете конвертировать документы из C#, из виджета или из собственного фронтенда.

Установить пакет

powershell
Install-Package Doconut.NETFramework.Converter

Держите пакет Converter той же версии, что и Doconut.NETFramework.

Зарегистрировать плагин

Каждый плагин регистрируется одинаково: AddPlugin<TPlugin>() в DoconutHost.Initialize.

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#

Каждая конверсия возвращает перемещаемый MemoryStream, позиционированный на 0. DocumentConverter без состояния: получайте его из DoconutHost.Services где бы вам это ни потребовалось и свободно переиспользуйте.

csharp
// Получите DocumentConverter из DoconutHost.Services; его конструктор internal, поэтому никогда не `new` его.
Stream pdf = await converter.ConvertAsync("contract.docx", ConversionTarget.Pdf, ct: ct);
csharp
// sourceExtension включает ведущую точку. password равно null, если только документ не защищён.
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: директива @Page должна иметь Async="true", а конверсия выполняется как асинхронная задача страницы.
// Блокировка на нём вместо этого (.Result, .Wait(), .GetAwaiter().GetResult()) приводит к взаимной блокировке запроса:
// он никогда не возвращается, и ничего не логируется.
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, а не HttpContext.Current: после await последнее может быть null.
            Context.ApplicationInstance.CompleteRequest();
        }
    }));
}

Без Async="true" в директиве @Page, 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, Image, Email, Diagram, Project, PSD, web document) есть собственный набор разрешённых целей. Не захардкодьте этот enum как список целей в вашем 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 её увидит: сохраняйте maxRequestLength (KB) и
// maxAllowedContentLength (bytes) в Web.config выше 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. В этом пакете конечные точки — обработчик DocImage.axd из Web.config.
  • Скрипт и конечные точки независимы. Модуль ресурсов обслуживает doconutConverter.js независимо от того, включён ли виджет. Без AddConverterWidget() виджет загружается, рисует свою зону перетаскивания и выглядит полностью корректно — и его первое действие отвечает 404.

Ограничения загрузки

ASP.NET читает всё тело запроса до того, как запустится DocImage.axd. Загрузка, превышающая MaxUploadMb, но ниже ограничений хоста, получает собственный JSON 413 виджета ("File is too large."). Загрузка, превышающая ограничения хоста, сначала отклоняется IIS с ошибочной страницей, которую виджет не может прочитать. Держите оба ограничения хоста в Web.config выше предела виджета:

xml
<system.web>
  <httpRuntime maxRequestLength="30720" />           <!-- КИЛОБАЙТЫ: 30 MB -->
</system.web>
<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="31457280" />  <!-- БАЙТЫ: 30 MB -->
    </requestFiltering>
  </security>
</system.webServer>

Настройка виджета

Параметры, передаваемые в Doconut.convert(selector, options):

ПараметрТипПо умолчаниюПримечания
basePathstring/doconutURL конечной точки. Установите '/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() удаляет его.

Создайте собственный фронтенд

Виджет является клиентом этого HTTP‑контракта на DocImage.axd:

МаршрутНазначениеОтвет при успехе
POST /DocImage.axd?convert=open (multipart, поле 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 — обычный токен просмотрщика, который действует в течение срока жизни сессии просмотрщика.

sourceExt в ответе open не имеет ведущей точки ("docx") — противоположно параметру sourceExtension функции ConvertAsync.

МаршрутСтатусКогдаТело
любой404Виджет не включён (AddConverterWidget() никогда не вызывался)только статус
любой405Неправильный глагол (open/run требуют POST, download требует GET)только статус
open413Загрузка превышает MaxUploadMb (но не ограничения IIS){ "error": "File is too large." }
open400Отсутствует multipart‑тело, файл или тип, который нельзя преобразовать{ "error": "..." }
open403Тип файла требует возможности, которую лицензия не предоставляет{ "error": "..." }
run400Некорректный токен, неизвестный 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 или лицензия без ConverterApplication_Start бросает исключение; каждый запрос отвечает пустым 500—
Просроченная временная/демо‑лицензияЗапускаетсяПреобразует, с водяным знаком оценки — watermarked: true

Интеграцию можно собрать и протестировать от начала до конца на оценочной лицензии перед покупкой — меняются только байты вывода.

Устранение неполадок

СимптомПроверка
Каждый запрос отвечает пустым 500 после добавления плагинаЛицензия предоставляет Converter — журнал событий Windows, источник ASP.NET
Разрешение DocumentConverter вызывает исключениеAddPlugin<ConverterPlugin>() находится в DoconutHost.Initialize
Преобразование Web Forms никогда не возвращает результатСтраница блокируется на ConvertAsync — используйте Async="true" и RegisterAsyncTask
Потоковое преобразование сообщает, что формат не поддерживаетсяsourceExtension содержит ведущую точку
Виджет загружается, но его запросы возвращают 404AddConverterWidget() не был вызван, или basePath не является '/DocImage.axd'
Большая загрузка показывает страницу ошибки IIS вместо сообщения виджетаmaxRequestLength / maxAllowedContentLength ниже MaxUploadMb
Отсутствует цельИспользуйте allowedTargets из convert=open
Срок загрузки истёкПовторите convert=open / convert=run; токены намеренно временные

Была ли эта страница полезной?