Bắt đầu nhanh

Hiển thị tài liệu đầu tiên của bạn trong MVC 5 hoặc Web Forms

Hướng dẫn này đưa một ứng dụng ASP.NET trên .NET Framework 4.7.2 từ gói đã cài đặt đến một tài liệu được hiển thị trong trình duyệt: Global.asax, một endpoint mở, các tài nguyên của viewer, widget và token kết nối chúng. Nó giả định các đăng ký Web.config từ Cài đặt đã có sẵn — nếu không có trình xử lý DocImage.axd và mô-đun tài nguyên, không có gì bên dưới có thể tiếp cận máy chủ.

Soạn Doconut trong Global.asax

Một Application_Start hoàn chỉnh cho ứng dụng MVC 5:

csharp
// Global.asax.cs - compose once per worker process, release on shutdown.
protected void Application_Start()
{
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

    Doconut.DoconutHost.Initialize(options =>
    {
        options.LicensePath = HostingEnvironment.MapPath("~/wwwroot/Doconut.Viewer.lic");
        options.UnsafeMode  = false; // a page is served only to the session that opened the document
    });

    // MVC only: keep the two Doconut endpoints out of routing.
    RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    RouteTable.Routes.IgnoreRoute("doconut-res/{*pathInfo}");
}

protected void Application_End()
{
    Doconut.DoconutHost.Shutdown();
}
  • Encoding.RegisterProvider làm cho các trang mã legacy mà một số định dạng tài liệu sử dụng có sẵn cho tiến trình. Gọi nó một lần, trước khi tài liệu đầu tiên được mở.
  • UnsafeMode = false là mặc định, được ghi rõ để không ai vô tình thay đổi: DocImage.axd phục vụ một trang chỉ cho phiên ASP.NET đã mở tài liệu.
  • Hai lời gọi IgnoreRoute giữ cho routing của MVC tránh DocImage.axd và /doconut-res/*, những thứ mà trình xử lý và mô-đun cung cấp. Ứng dụng Web Forms không có route MVC nên không cần dòng này.
  • DoconutHost.Shutdown dừng công việc nền của Doconut và giải phóng các dịch vụ khi pool ứng dụng được tái tạo. Việc gọi nó vẫn an toàn ngay cả khi Initialize chưa bao giờ chạy.

Mở một tài liệu

Mở một tài liệu sẽ trả về một token không trong suốt. Widget gửi token này cùng với mỗi yêu cầu trang.

Trong MVC 5, một hành động controller giải quyết dịch vụ Viewer từ DoconutHost.Services — MVC 5 không có injection qua constructor cho nó:

csharp
// POST /Document/Open
[HttpPost]
public async Task<ActionResult> Open()
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();

    // The token is opaque - hand it to the viewer, never log or persist it.
    string token = await viewer.OpenDocumentAsync(Server.MapPath("~/files/Sample.pdf"));
    return Json(new { token });
}

Tài liệu phải được mở trong một yêu cầu có trạng thái phiên, vì việc mở nó là cách cấp quyền truy cập các trang cho phiên ASP.NET hiện tại. Các controller MVC và trang Web Forms mặc định có session state. Một IHttpHandler tùy chỉnh không có, trừ khi nó triển khai System.Web.SessionState.IRequiresSessionState. Mở tài liệu từ một handler mà không có session, và viewer sẽ hiển thị "You Are Not Authorized To View This Page." trên mỗi trang.

Trong Web Forms, trang có thể mở tài liệu trong code-behind của nó. OpenDocument là dạng đồng bộ của OpenDocumentAsync, dành cho code-behind không sử dụng trang bất đồng bộ:

csharp
// Default.aspx.cs
protected string Token;

protected void Page_Load(object sender, EventArgs e)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    Token = viewer.OpenDocument(Server.MapPath("~/files/Sample.pdf"));
}

Xử lý token như một chứng chỉ bearer: không bao giờ ghi log hoặc lưu trữ nó; chỉ truyền cho widget. Nó sẽ ngừng hoạt động khi phiên tài liệu hết hạn — mở lại tài liệu để nhận token mới.

Tham chiếu tài nguyên của viewer

Viewer.ReferenceCss và Viewer.ReferenceScripts tạo các thẻ <link> và <script> cho các tài nguyên nhúng, theo thứ tự phụ thuộc, trỏ tới /doconut-res/. Widget là một plugin jQuery, vì vậy jQuery được đưa lên trước.

MVC 5. Views không có @inject, vì vậy tạo các thẻ trong controller và truyền chúng qua ViewBag:

csharp
public ActionResult Index()
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    ViewBag.ViewerCss = viewer.ReferenceCss(new CssConfig
    {
        IncludeBootstrapCss = true,
        IncludeViewerCss    = true
    });
    ViewBag.ViewerScripts = viewer.ReferenceScripts(new ScriptConfig
    {
        IncludeJQuery        = true,
        IncludeBootstrap     = true,
        IncludeViewerScripts = true
    });
    return View();
}
html
@Html.Raw(ViewBag.ViewerCss)
...
@Html.Raw(ViewBag.ViewerScripts)

Giữ công việc này trong controller. MVC 5 biên dịch các view khi chúng được phục vụ lần đầu, và nếu không có gói biên dịch bổ sung, trình biên dịch chỉ hiểu C# 5 — không có ?., không có $"...", không có nameof. Một view sử dụng chúng sẽ biên dịch sạch trong Visual Studio, sau đó trả về lỗi 500 trống trong trình duyệt.

Web Forms. Phơi bày các chuỗi tương tự từ code-behind và viết chúng bằng <%= %>:

html
<head>
    <%= ViewerCss %>
</head>
<body>
    ...
    <%= ViewerScripts %>
</body>

IncludeViewerCss và IncludeViewerScripts là phần cốt lõi bắt buộc. Để thêm các ribbon Tìm kiếm và Ghi chú, cũng đặt IncludeSearchCss, IncludeAnnotationCss, IncludeSearchScripts, IncludeSearchBar, IncludeAnnotationScripts và IncludeAnnotationBar. Cả hai phương pháp đều bỏ qua các tệp của một ribbon khi giấy phép không cho phép tính năng đó; viewer vẫn khởi động.

Thêm viewer vào một trang

Bề mặt render là hai div lồng nhau:

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

Đặt chiều cao thực cho div bên ngoài — viewer sẽ lấp đầy container và đo kích thước khi khởi động. Viewer không tự vẽ thanh công cụ của mình: GotoPage, Next, Zoom, FitType và các phương thức còn lại là những hàm mà trang của bạn gắn vào các nút riêng, đặt bên cạnh 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.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>
</nav>

Khởi tạo viewer và hiển thị tài liệu

Sau các script của viewer, tạo widget, sau đó truyền token cho nó:

javascript
var objViewer = $('#div_ctlDoc').docViewer({
    showThumbs: true,
    autoLoad:   false,
    pageZoom:   100,
    FitType:    'width',
    BasePath:   '/',                   // '/' means DocImage.axd at the site root
    ResPath:    'doconut-res/images',  // no leading '/'
    onViewerReady: function () {
        // pages are visible; hide a loading indicator here
    },
    onError: function (message) {
        console.error('Doconut viewer error:', message);
    }
});

fetch('/Document/Open', { method: 'POST' })
    .then(function (resp) { return resp.json(); })
    .then(function (data) { objViewer.View(data.token); });

Trong Web Forms, nơi trang mở tài liệu trực tiếp, ba dòng cuối cùng trở thành objViewer.View('<%= Token %>');.

Bốn chi tiết sau sẽ thất bại mà không có thông báo lỗi:

  • BasePath: '/' chỉ tới DocImage.axd ở gốc site. Đối với ứng dụng trong thư mục ảo, dùng tên mà không có dấu gạch chéo ('myapp' sẽ yêu cầu /myapp/DocImage.axd). Dấu gạch chéo đầu ('/myapp') tạo ra //myapp/DocImage.axd, mà trình duyệt hiểu là yêu cầu tới một host khác.
  • ResPath: 'doconut-res/images' — các biểu tượng nằm một thư mục dưới thư mục tài nguyên gốc. Khi dùng 'doconut-res', mọi biểu tượng trả về 404: không có glyph thanh công cụ và không có spinner tải, trong khi tài liệu vẫn được render. Dấu gạch chéo đầu gây lỗi tương tự như BasePath.
  • Callbacks là các tùy chọn. onViewerReady, onError và các tùy chọn còn lại phải nằm trong đối tượng truyền cho docViewer(...). Một hàm toàn cục đặt tên theo viewer, như ctlDoc_ViewerReady, sẽ không bao giờ được gọi.
  • Giữ viewer, sau đó gọi View riêng. docViewer(...) trả về đối tượng có Zoom, Next và FitType; View(...) trả về một thứ khác. var objViewer = $('#div_ctlDoc').docViewer({...}).View(token) sẽ render tài liệu, sau đó gây lỗi objViewer.Zoom is not a function khi nhấn nút toolbar đầu tiên.

Tên tùy chọn hỗn hợp camelCase (showThumbs, autoLoad, pageZoom) và PascalCase (FitType, BasePath, ResPath). Một tùy chọn bị viết sai sẽ bị bỏ qua, và widget sẽ sử dụng giá trị mặc định.

Giải pháp một dòng

Đối với một trang chỉ cần hiển thị tài liệu, mà không có thanh công cụ riêng, Viewer.RenderViewer ghi toàn bộ script khởi tạo — docViewer(...) với BasePath: '/' và ResPath: 'doconut-res/images', sau đó .View(token). Nó chỉ tồn tại trong gói này:

csharp
// Web Forms code-behind, after opening the document
ViewerInit = viewer.RenderViewer("div_ctlDoc", Token);
html
<div id="div_ctlDoc" style="height: 100vh"></div>
<%= ViewerScripts %>
<%= ViewerInit %>

Bạn có thể truyền một ViewerConfig làm đối số thứ ba để thay đổi mức thu phóng, cách hiển thị, hoặc bảng thu nhỏ. Vì script được tạo ra không giữ tham chiếu tới viewer, hãy sử dụng khởi tạo viết tay ở trên khi trang của bạn cần thanh công cụ riêng.

Đóng tài liệu

Gọi objViewer.Close(true) khi người dùng rời khỏi viewer hoặc mở một tài liệu thay thế: nó sẽ xóa widget và gửi yêu cầu đóng tới DocImage.axd. objViewer.Close() không có đối số chỉ xóa widget. Trên máy chủ, viewer.CloseDocument(token) sẽ xóa ngay phiên tài liệu, giải phóng tài nguyên render và thu hồi quyền truy cập của phiên vào các trang. Thời gian hết hạn trượt cuối cùng cũng làm điều tương tự, nhưng việc đóng tài liệu lớn một cách rõ ràng sẽ giải phóng bộ nhớ sớm hơn.

Chạy nó

Đặt một file PDF tại ~/files/Sample.pdf, khởi động site trong IIS Express (F5 trong Visual Studio), và mở trang chứa widget. Trang đầu tiên sẽ được render, với các hình thu nhỏ ở phía bên trái. Nếu không, xem phần 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 sẽ không gây lỗi. Viewer vẫn render bình thường, nhưng mỗi trang sẽ có dấu watermark đánh giá. Xem Cài đặt giấy phép để biết Doconut tìm giấy phép ở đâu.

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