Plugin Chuyển Đổi

Chuyển đổi tài liệu sang 24 định dạng mục tiêu

Plugin Converter biến Doconut thành một dịch vụ chuyển đổi tài liệu. Nó cung cấp động cơ phía sau lớp mặt nạ công khai DocumentConverter và — khi chọn — một widget có thể chèn được được phục vụ bởi DocImage.axd, vì vậy bạn có thể chuyển đổi tài liệu từ C#, từ widget, hoặc từ giao diện người dùng mà bạn tự viết.

Cài đặt gói

powershell
Install-Package Doconut.NETFramework.Converter

Giữ phiên bản gói Converter đồng nhất với Doconut.NETFramework.

Đăng ký plugin

Mỗi plugin được đăng ký theo cùng một cách: AddPlugin<TPlugin>() trong DoconutHost.Initialize.

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

Nếu không có giấy phép hoạt động nào cấp khả năng Converter — không có giấy phép, tệp TRIAL cũ, hoặc giấy phép không có khả năng này — DoconutHost.Initialize sẽ ném ngoại lệ từ Application_Start. Trong System.Web điều này có nghĩa là mỗi yêu cầu trả về một lỗi 500 trống, và lý do chỉ được ghi trong nhật ký sự kiện Windows (nguồn ASP.NET). Các giấy phép Demo/NFR tạm thời được chấp nhận. Xem Cài Đặt Giấy Phép.

Chuyển đổi từ C#

Mỗi lần chuyển đổi trả về một MemoryStream có thể tìm kiếm và được đặt vị trí tại 0. DocumentConverter không có trạng thái: lấy nó từ DoconutHost.Services bất cứ nơi nào bạn cần và tái sử dụng tự do.

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);

Hai chi tiết dễ bị nhầm lẫn: sourceExtension trong phiên bản overload của stream phải bao gồm dấu chấm đầu (".xlsx", không phải "xlsx"), nếu không sẽ không được giải quyết. Và mặc dù tên gọi, WordToHtmlAsync trả về Task<Stream>, không phải Task<string> — tài liệu HTML (hình ảnh được nhúng dưới dạng Base64) sẽ đến dưới dạng stream, giống như mọi kết quả khác.

Trong một controller MVC 5

Biến hành động thành bất đồng bộ và await quá trình chuyển đổi:

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");
}

Trong Web Forms: không bao giờ chặn khi chuyển đổi

Một trang Web Forms chạy trên ngữ cảnh yêu cầu đơn luồng. ConvertAsync sẽ tiếp tục trên ngữ cảnh đó khi hoàn thành, vì vậy một trang chờ đồng bộ — .Result, .Wait(), .GetAwaiter().GetResult() — sẽ giữ lại luồng mà quá trình chuyển đổi cần để kết thúc. Yêu cầu sẽ không bao giờ trả về. Trình duyệt vẫn quay vòng, IIS không ghi log gì, và phần còn lại của site vẫn hoạt động, vì vậy nó trông giống như một quá trình chuyển đổi chậm chứ không phải deadlock.

Đánh dấu trang với Async="true" và chạy quá trình chuyển đổi như một tác vụ bất đồng bộ của trang:

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();
        }
    }));
}

Nếu không có Async="true" trong chỉ thị @Page, RegisterAsyncTask sẽ bị bỏ qua một cách im lặng. Sau khi await, hãy sử dụng Context của trang; HttpContext.Current có thể là null ở đó.

Định dạng mục tiêu

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

Không phải mọi nguồn đều chuyển đổi được sang mọi đích: mỗi họ định dạng nguồn (Word, Excel, PowerPoint, PDF, CAD, Image, Email, Diagram, Project, PSD, web document) có bộ đích cho phép riêng. Đừng hardcode enum này làm danh sách đích cho UI của bạn: ?convert=open trả về allowedTargets cho tệp đã tải lên, và đó là những gì nên điều khiển bộ chọn.

Widget nhúng

Các endpoint ?convert=open|run|download của widget được phục vụ bởi DocImage.axd và mặc định là tắt. Kích hoạt chúng bên cạnh việc đăng ký plugin:

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 reads the whole upload before Doconut sees it: keep maxRequestLength (KB) and
// maxAllowedContentLength (bytes) in Web.config above the widget's 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>

Hai điều cần kiểm tra khi widget không hoạt động đúng:

  • basePath: '/DocImage.axd'. Đường dẫn mặc định của widget là /doconut, là endpoint của ASP.NET Core. Trong gói này, các endpoint là trình xử lý DocImage.axd từ Web.config.
  • Script và các endpoint hoạt động độc lập. Module tài nguyên phục vụ doconutConverter.js bất kể widget có được bật hay không. Nếu không gọi AddConverterWidget(), widget vẫn tải, vẽ vùng thả, và trông hoàn toàn đúng — nhưng hành động đầu tiên của nó trả về 404.

Giới hạn tải lên

ASP.NET đọc toàn bộ thân yêu cầu trước khi DocImage.axd chạy. Một tải lên vượt quá MaxUploadMb nhưng dưới giới hạn của máy chủ sẽ nhận JSON 413 của widget ("File is too large."). Một tải lên vượt quá giới hạn máy chủ sẽ bị IIS từ chối trước, với trang lỗi mà widget không thể đọc được. Giữ cả hai giới hạn máy chủ trong Web.config phía trên giới hạn của widget:

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>

Tùy chỉnh widget

Các tùy chọn được truyền vào Doconut.convert(selector, options):

Tùy chọnKiểuMặc địnhGhi chú
basePathstring/doconutĐịa chỉ endpoint. Đặt '/DocImage.axd' cho gói này
resPathstring/doconut-resĐược chấp nhận để đồng nhất với các widget Doconut khác; widget chuyển đổi không xây dựng URL nào từ nó
maxUploadMbnumber25Chỉ kiểm tra trước phía client; server sẽ áp dụng giới hạn riêng
licenseUrlstring | nullnullBiến thông báo watermark trên màn hình kết quả thành một liên kết
labelsobject{}Ghi đè bất kỳ chuỗi tiếng Anh nào của widget

Hàm gọi lại:

Hàm gọi lạiKhi nào được kích hoạtDữ liệu
onReady()Widget đã hiển thị màn hình thả—
onSourceLoaded({ token, pages, sourceExt, allowedTargets })?convert=open thành côngToken nguồn, số trang, phần mở rộng nguồn (không có dấu chấm), các đích cho phép
onConverted({ downloadToken, resultToken, resultPages, downloadName, watermarked, target })?convert=run thành côngPhản hồi chạy cộng với target được yêu cầu
onDownload({ downloadName, downloadToken })Người dùng nhấn Tải xuốngKích hoạt cùng với việc tải xuống của trình duyệt
onError({ phase, message })Yêu cầu mở hoặc chạy thất bạiphase là 'open' hoặc 'run'; message là lỗi máy chủ đã được làm sạch

Doconut.convert() trả về thể hiện của widget: reset() quay lại màn hình thả, loadFile(file) bắt đầu quy trình với một File, và destroy() loại bỏ nó.

Xây dựng giao diện phía trước của bạn

Widget là một client cho hợp đồng HTTP này trên DocImage.axd:

Đường dẫnMục đíchPhản hồi thành công
POST /DocImage.axd?convert=open (multipart, trường file)Tải lên và mở tài liệu nguồn200 — { token, pages, sourceExt, allowedTargets }
POST /DocImage.axd?token=<token>&convert=run&target=<ext>Chuyển đổi nguồn đã tải lên sang target200 — { downloadToken, resultToken, resultPages, downloadName, watermarked }
GET /DocImage.axd?convert=download&token=<downloadToken>Phát luồng tệp đã chuyển đổi200 — tệp, Content-Disposition: attachment

Các nguồn đã tải lên được giữ trong 30 phút; sau thời gian đó, run trả về 404 và tệp phải được mở lại. downloadToken có thời gian 30 phút riêng khi quá trình chuyển đổi hoàn tất. resultToken là một token xem thông thường, kéo dài suốt thời gian phiên xem.

sourceExt trong phản hồi open không có dấu chấm đầu ("docx") — ngược lại với tham số sourceExtension của ConvertAsync.

Đường dẫnTrạng tháiKhiNội dung
bất kỳ404Widget không được bật (AddConverterWidget() chưa bao giờ được gọi)chỉ trạng thái
bất kỳ405Phương thức sai (open/run yêu cầu POST, download yêu cầu GET)chỉ trạng thái
open413Tải lên vượt quá MaxUploadMb (nhưng không vượt quá giới hạn IIS){ "error": "File is too large." }
open400Không có body multipart, không có file, hoặc loại không thể chuyển đổi{ "error": "..." }
open403Loại file yêu cầu một khả năng mà giấy phép không cấp{ "error": "..." }
run400Token sai dạng, target không xác định, hoặc target không nằm trong allowedTargets{ "error": "..." }
run404Tải lên đã hết hạn hoặc chưa bao giờ được mở{ "error": "Upload expired — please re-open the file." }
open, run500Xử lý thất bại{ "error": "<sanitized message>" }
download400 / 404Token tải xuống sai dạng, không tồn tại hoặc đã hết hạnchỉ trạng thái

Quyền sở hữu tài nguyên

Người gọi sở hữu MemoryStream mà quá trình chuyển đổi trả về và nên giải phóng nó sau khi sao chép hoặc trả về (MVC's File(stream, …) sẽ giải phóng cho bạn). Không tự tạo hoặc giải phóng DocumentConverter — hàm khởi tạo của nó là nội bộ.

Đánh dấu nước

Trạng thái giấy phépKhởi độngKết quả chuyển đổi
Giấy phép xem trả phí cho phép ConverterBắt đầuSạch — watermarked: false
Giấy phép Tạm thời/Demo đang hoạt độngBắt đầuChuyển đổi, có dấu nước đánh giá — watermarked: true
Không có giấy phép, tệp TRIAL cũ, hoặc giấy phép không có ConverterApplication_Start ném lỗi; mọi yêu cầu trả về 500 trống—
Giấy phép Tạm thời/Demo đã hết hạnBắt đầuChuyển đổi, có dấu nước đánh giá — watermarked: true

Một tích hợp có thể được xây dựng và kiểm thử toàn diện trên giấy phép đánh giá trước khi mua — chỉ có các byte đầu ra thay đổi.

Khắc phục sự cố

Triệu chứngKiểm tra
Mọi yêu cầu trả về 500 trống sau khi thêm pluginGiấy phép cho phép Converter — kiểm tra nhật ký sự kiện Windows, mã nguồn ASP.NET
Giải quyết DocumentConverter ném lỗiAddPlugin<ConverterPlugin>() nằm trong DoconutHost.Initialize
Quá trình chuyển đổi Web Forms không bao giờ trả vềTrang bị chặn ở ConvertAsync — sử dụng Async="true" và RegisterAsyncTask
Chuyển đổi luồng báo định dạng không được hỗ trợsourceExtension có dấu chấm đầu
Widget tải lên nhưng các yêu cầu trả về 404AddConverterWidget() chưa được gọi, hoặc basePath không phải là '/DocImage.axd'
Tải lên lớn hiển thị trang lỗi IIS thay vì thông báo của widgetmaxRequestLength / maxAllowedContentLength thấp hơn MaxUploadMb
Thiếu mục tiêuSử dụng allowedTargets từ convert=open
Tải xuống đã hết hạnLặp lại convert=open / convert=run; các token là tạm thời theo mục đích

Trang này có hữu ích không?