Bắt đầu nhanh

Hiển thị tài liệu đầu tiên của bạn trong vài phút

Hướng dẫn này đưa một ứng dụng ASP.NET Core từ một Program.cs trống tới một tài liệu được hiển thị trong trình duyệt: đăng ký máy chủ, gói Viewer đầy đủ (thanh công cụ Viewer, phần gắn Viewer và các ribbon Tìm kiếm/Chú thích tùy chọn), tham chiếu tài nguyên, khởi tạo client, mở tài liệu và thực thi.

Ứng dụng host phải nhắm tới môi trường chạy hỗ trợ netstandard2.1 — .NET Core 3.0 trở lên. Các ứng dụng tham chiếu được phát hành cùng bản này nhắm tới net8.0.

Cấu hình máy chủ

AddDoconut() registers the services; UseDoconutResources() and UseDoconut() wire the middleware. The resources call must come first. The session calls are required too — Doconut's default document security validates every page request against ASP.NET session state. Session state needs a cache: AddDistributedMemoryCache() provides one. An MVC or Razor Pages app already registers it, but a minimal-API app does not, and without it the app stops at startup with Unable to resolve service for type 'IDistributedCache'. Already registered Doconut during Installation? Skip ahead to the next section.

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // trạng thái phiên cần một bộ nhớ cache (MVC/Razor Pages đã thêm sẵn một bộ)
builder.Services.AddSession(); // bảo mật tài liệu Doconut dựa trên trạng thái phiên ASP.NET

app.UseSession();          // gọi UseSession() trước UseDoconut()
app.UseDoconutResources(); // phải được đăng ký trước UseDoconut()
app.UseDoconut();

Ứng dụng tham chiếu giữ nguyên dạng yêu cầu lịch sử DocImage.axd và gắn middleware tài liệu vào một nhánh phù hợp. Đây là cấu trúc cần sao chép khi bạn đưa một ứng dụng .NET Standard hiện có lên, vì client vẫn gọi cùng một URL như trước đây:

csharp
builder.Services.AddDoconut(options =>
{
    options.UnsafeMode      = false;   // giữ kiểm tra phiên của Doconut được bật
    options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // trạng thái phiên cần một bộ nhớ cache (MVC/Razor Pages đã thêm sẵn một bộ)
builder.Services.AddSession(options =>
{
    options.IdleTimeout        = TimeSpan.FromSeconds(3600);
    options.Cookie.HttpOnly    = true;
    options.Cookie.IsEssential = true;
});

// Cần thiết cho các mã hoá đa byte trong một số định dạng tài liệu.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseDoconutResources();   // TRƯỚC nhánh tài liệu
app.MapWhen(
    context => context.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
    branch => branch.UseDoconut());

UseDoconut() không nhận bất kỳ tùy chọn nào trong phiên bản này — mọi thứ đã được chuyển sang AddDoconut(). MiddlewarePath là một giá trị phối hợp; nó không tự mình ánh xạ một nhánh ASP.NET Core. ResourcesPath phục vụ gói nhúng tại /doconut-res, và đường dẫn tài nguyên hình ảnh của widget do đó là ResPath: 'doconut-res/images'.

Thêm trình xem vào trang

Viewer là phần lõi cần thiết của trang. Bề mặt hiển thị của nó sử dụng hai div lồng nhau:

html
<div id="divDocViewer">
    <div id="div_ctlDoc"></div>
</div>

Xem thanh công cụ, các phần gắn mô-đun và bề mặt Viewer như một thành phần trang duy nhất. Tìm kiếm và Chú thích chèn các ribbon nhúng của chúng vào các phần gắn tùy chọn, nhưng các mô-đun này không bao giờ độc lập: chúng luôn được gắn vào Viewer trên cùng một trang.

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Viewer navigation, zoom, Search, and Annotation buttons -->
</nav>

<div id="searchBarMount"></div>
<div id="annBarMount"></div>

<div id="divDocViewer">
    <div id="div_ctlDoc"></div>
</div>

Tham chiếu tài nguyên của trình xem

Trong một Razor view, dịch vụ Viewer được tiêm sẽ phát ra các thẻ <link> và <script> của trình xem theo thứ tự phụ thuộc — widget là một plugin jQuery, vì vậy jQuery phải được tải trước các script của trình xem:

html
@inject Doconut.Viewer Viewer

@Html.Raw(Viewer.ReferenceCss(new CssConfig
{
    IncludeBootstrapCss = true,
    IncludeViewerCss    = true
}))

@Html.Raw(Viewer.ReferenceScripts(new ScriptConfig
{
    IncludeJQuery        = true,
    IncludeBootstrap     = true,
    IncludeViewerScripts = true
}))

Đối với gói Viewer đầy đủ, yêu cầu các tài nguyên Viewer và mô-đun cùng nhau:

html
@Html.Raw(Viewer.ReferenceCss(new CssConfig
{
    IncludeBootstrapCss  = true,
    IncludeViewerCss     = true,
    IncludeSearchCss     = true,
    IncludeAnnotationCss = true
}))

@Html.Raw(Viewer.ReferenceScripts(new ScriptConfig
{
    IncludeJQuery            = true,
    IncludeBootstrap         = true,
    IncludeViewerScripts     = true,
    IncludeSearchScripts     = true,
    IncludeSearchBar         = true,
    IncludeAnnotationScripts = true,
    IncludeAnnotationBar     = true
}))

IncludeViewerCss và IncludeViewerScripts là các cờ lõi bắt buộc. Không bao giờ công bố một ví dụ Ribbon Tìm kiếm hoặc Chú thích mà không có chúng, phần mount của Viewer, và một thể hiện docViewer. ReferenceCss và ReferenceScripts sẽ bỏ qua các tài nguyên của mô-đun tùy chọn khi giấy phép hiện tại không cho phép tính năng đó; core Viewer vẫn sẽ khởi động.

Các lời gọi này thay thế các file script được sao chép thủ công. Nếu ứng dụng vẫn tải docViewer.js, documentLinks.js, hoặc docViewer.UI.js của riêng mình, hãy loại bỏ các thẻ đó một khi trang hoạt động từ các tài nguyên được phát ra — xem hướng dẫn di chuyển.

Khởi tạo trình xem

Widget phía client là một plugin jQuery. Đây là một tập hợp tối thiểu các tùy chọn khởi tạo thực tế (không phải pseudocode):

javascript
let searchBar = null;
let annBar = null;

const objViewer = $('#div_ctlDoc').docViewer({
    showThumbs: true,
    autoLoad:   false,
    pageZoom:   100,
    FitType:    'width',
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    onViewerReady: function () {
        // các trang đã hiển thị; an toàn để ẩn biểu tượng tải ở đây
    },
    // Chuyển tiếp các sự kiện vòng đời chú thích tới ribbon nhúng khi nó có mặt.
    onAnnLoaded:    () => annBar?.handleAnnLoaded(),
    onAnnSaved:     () => annBar?.handleAnnSaved(),
    onAnnSaveError: () => annBar?.handleAnnSaveError(),
    onAnnClosed:    () => annBar?.handleAnnClosed(),
    onError: function (message) {
        console.error('Doconut viewer error:', message);
    }
});

Hai chi tiết quyết định liệu trang này có hoạt động hay không:

  • Callbacks là các tùy chọn. onViewerReady, onViewerBusy, onCopy, và onAutoLoadStatus được truyền trong đối tượng tùy chọn. Trình xem trước đây suy ra tên hàm toàn cục từ id container và gọi chúng; phiên bản này không làm như vậy. Một handler được khai báo chỉ là hàm toàn cục sẽ không bao giờ chạy, và không có gì được báo cáo.
  • ResPath chỉ tới tiền tố tài nguyên, không phải gốc của ứng dụng. Với ResourcesPath mặc định, giá trị này là doconut-res/images. Nếu bạn đã thay đổi ResourcesPath, hãy sử dụng tiền tố của riêng bạn.

Cả hai lỗi này đều im lặng — xem Khắc phục sự cố và hướng dẫn di chuyển.

Cách viết hoa tên tùy chọn thực sự hỗn hợp — showThumbs, autoLoad và pageZoom là camelCase, nhưng FitType, BasePath và ResPath là PascalCase. Không có quy tắc nhất quán; nếu viết sai kiểu chữ, tùy chọn sẽ bị bỏ qua một cách im lặng (widget sẽ quay lại giá trị mặc định thay vì ném lỗi).

Tập hợp gói Viewer đầy đủ

Cả hai ứng dụng tham chiếu đều cài đặt các phần sau cùng nhau trên một trang:

Phần của góiYêu cầuCách kết nối
Tài nguyên Viewer, mount, và objViewerBắt buộcBộ render tài liệu lõi
Thanh công cụ ViewerBắt buộc trong cấu hình tham chiếuMarkup host; các nút gọi cùng objViewer
Ribbon tìm kiếmTùy chọn, mô-đun có giấy phépdoconutSearchBar(...).attach(objViewer)
Ribbon chú thíchTùy chọn, mô-đun có giấy phépdoconutAnnotationBar(...).attach(objViewer)

Mặc dù thanh công cụ Viewer chính là markup host, nó được cài đặt cùng với Viewer và không bao giờ được tài liệu hoá như một điều khiển riêng lẻ. Điều này giữ cho bố cục, nhãn, biểu tượng và quy tắc ủy quyền của nó dưới sự kiểm soát của ứng dụng của bạn trong khi mỗi nút vẫn điều khiển cùng một thể hiện Viewer:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <button type="button" onclick="objViewer.GotoPage(1)">First</button>
    <button type="button" onclick="objViewer.Next(false)">Previous</button>
    <button type="button" onclick="objViewer.Next(true)">Next</button>
    <button type="button" onclick="objViewer.GotoPage(objViewer.TotalPages())">Last</button>
    <button type="button" onclick="objViewer.Zoom(false)">Zoom out</button>
    <button type="button" onclick="objViewer.Zoom(true)">Zoom in</button>
    <button type="button" onclick="objViewer.FitType('width')">Fit width</button>
    <button type="button" onclick="objViewer.FitType('height')">Fit height</button>
    <button type="button" id="openSearch">Search</button>
    <button type="button" id="openAnnotations">Annotations</button>
</nav>

Giữ thứ tự khởi tạo gói như được sử dụng bởi cả hai ứng dụng tham chiếu:

  1. Phát ra CSS cho Viewer và các mô-đun có giấy phép.
  2. Render thanh công cụ Viewer, các mount Search/Annotation, và mount Viewer cùng nhau.
  3. Phát ra script cho Viewer và các mô-đun có giấy phép.
  4. Tải script trợ giúp thanh công cụ riêng của ứng dụng host.
  5. Khởi tạo docViewer và giữ lại objViewer đã tạo.
  6. Khởi tạo mỗi Ribbon Search hoặc Annotation có giấy phép.
  7. Gọi attach(objViewer) trên mọi Ribbon.
  8. Mở tài liệu và giữ token của nó cho các yêu cầu Viewer và mô-đun.

Các guard phía server rất quan trọng: khi một khả năng tùy chọn không khả dụng, script của nó sẽ không được phát ra, do đó hàm plugin jQuery của nó không tồn tại.

html
<script>
    let currentToken = '';

    const refitViewer = () =>
        requestAnimationFrame(() => objViewer.Refit());

    @if (Viewer.IsSearchEnabled)
    {
        <text>
    searchBar = $('#searchBarMount').doconutSearchBar({
        docId: 'ctlDoc',
        getRequestParams: () => ({ token: currentToken }),
        onLayout: refitViewer
    });
    searchBar.attach(objViewer);
        </text>
    }

    @if (Viewer.IsAnnotationEnabled)
    {
        <text>
    annBar = $('#annBarMount').doconutAnnotationBar({
        docId: 'ctlDoc',
        getRequestParams: () => ({ token: currentToken }),
        onLayout: refitViewer
    });
    annBar.attach(objViewer);
        </text>
    }

    document.getElementById('openSearch').addEventListener('click', () => {
        if (!searchBar) return;
        searchBar.isOpen() ? searchBar.close() : searchBar.open();
    });

    document.getElementById('openAnnotations').addEventListener('click', () => {
        if (!annBar) return;
        annBar.isOpen() ? annBar.close() : annBar.open();
    });
</script>

Cả hai thành phần nhúng đều tạo ra DOM Ribbon riêng của chúng. Search chứa các nhóm Find, Options và Results. Annotation chứa các công cụ tạo, điều khiển kiểu, hành động lưu và các hành động xuất/ảnh tùy chọn. Các thanh này cung cấp open(), close(), reset(), và isOpen(); luôn gọi attach(objViewer) một lần sau khi tạo chúng.

Ví dụ trên bỏ qua các callback host tùy chọn và các endpoint xuất/ảnh của Annotation để giữ khởi động tối thiểu. Xem Tìm kiếm và Chú thích để biết thiết lập đầy đủ cho từng tính năng, hoặc Chủ đề tùy chỉnh để tạo kiểu hoặc thay thế thanh công cụ Viewer do host sở hữu.

Mở tài liệu

Phía máy chủ có một endpoint: dịch vụ Viewer được tiêm sẽ mở tài liệu và trả về một token phiên. Việc mở là bất đồng bộ trong phiên bản này.

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // Mã thông báo là không trong suốt — chuyển nó cho widget, không bao giờ ghi log hoặc lưu trữ nó.
    string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
    return Results.Ok(new { token });
});

Phía client lấy token đó và chuyển nó cho widget bằng objViewer.View(token):

javascript
fetch('/api/open', { method: 'POST' })
    .then(resp => resp.json())
    .then(data => {
        currentToken = data.token;
        objViewer.View(currentToken);
    });

Không có gì cần khởi động trước lần mở đầu tiên. Nếu bạn đang nâng cấp một ứng dụng, hãy xóa mọi lời gọi InitCache() và cờ bảo vệ nó — phương thức này không còn tồn tại nữa.

Đóng tài liệu

Gọi objViewer.Close() khi người dùng rời khỏi viewer hoặc mở một tài liệu thay thế. Trong các quy trình làm việc do máy chủ điều khiển, viewer.CloseDocument(token) ngay lập tức xóa phiên được lưu trong bộ nhớ đệm, giải phóng engine render, xóa dấu bảo mật và thu hồi token. Thời gian hết hạn trượt cuối cùng sẽ thực hiện cùng một quá trình dọn dẹp, nhưng việc đóng rõ ràng được khuyến nghị cho các tài liệu lớn.

Luồng yêu cầu đã hoàn thành là:

text
AddDoconut + middleware
    -> render CSS/scripts và gắn thẻ div
    -> khởi tạo docViewer
    -> OpenDocumentAsync
    -> trả về token không trong suốt
    -> objViewer.View(token)
    -> yêu cầu page/search/annotation
    -> Close / CloseDocument

Xử lý token như một chứng chỉ mang: không bao giờ ghi log, không bao giờ lưu trữ, chỉ chuyển nó cho widget. Nó xác định một phiên tài liệu đang hoạt động trên máy chủ và sẽ ngừng hoạt động khi phiên đó hết hạn — mở lại tài liệu để nhận một token mới.

Chạy nó

Đặt một file PDF tại wwwroot/files/Sample.pdf, chạy dotnet run, và mở trang chứa widget. Trang đầu tiên sẽ được render trong viewer, với bảng thu nhỏ ở phía trái. Nếu không, xem Khắc phục sự cố.

Những gì bạn nhận được khi không có giấy phép

Một giấy phép thiếu không gây lỗi. Viewer vẫn render bình thường, nhưng mỗi trang sẽ có watermark đánh giá. Xem Cài đặt giấy phép để biết Doconut tìm giấy phép như thế nào và những gì sẽ thay đổi khi nó được tìm thấy.

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