Γρήγορη Εκκίνηση

Αποδώστε το πρώτο σας έγγραφο σε λίγα λεπτά

Αυτός ο οδηγός μετατρέπει μια εφαρμογή ASP.NET Core από ένα κενό Program.cs σε ένα έγγραφο που αποδίδεται στον περιηγητή: εγγραφή διακομιστή, το πλήρες πακέτο Viewer (γραμμή εργαλείων Viewer, τοποθέτηση Viewer και προαιρετικές ταινίες Αναζήτησης/Σχόλιου), αναφορές πόρων, αρχικοποίηση πελάτη, άνοιγμα εγγράφου και εκτέλεση.

Η εφαρμογή υποδοχής πρέπει να στοχεύει σε χρόνο εκτέλεσης που υποστηρίζει netstandard2.1 — .NET Core 3.0 ή νεότερο. Οι εφαρμογές αναφοράς που παρέχονται με αυτήν την έκδοση στοχεύουν net8.0.

Ρύθμιση διακομιστή

AddDoconut() καταχωρεί τις υπηρεσίες· UseDoconutResources() και UseDoconut() συνδέουν το middleware. Η κλήση των πόρων πρέπει να γίνει πρώτη. Οι κλήσεις συνεδρίας είναι επίσης απαραίτητες — η προεπιλεγμένη ασφάλεια εγγράφων Doconut επικυρώνει κάθε αίτημα σελίδας έναντι της κατάστασης συνεδρίας ASP.NET. Η κατάσταση συνεδρίας χρειάζεται μια κρυφή μνήμη: AddDistributedMemoryCache() παρέχει μία. Μια εφαρμογή MVC ή Razor Pages την καταχωρεί ήδη, αλλά μια ελάχιστη εφαρμογή API δεν το κάνει, και χωρίς αυτήν η εφαρμογή σταματά στην εκκίνηση με Unable to resolve service for type 'IDistributedCache'. Έχετε ήδη καταχωρήσει το Doconut κατά την Installation; Προχωρήστε στην επόμενη ενότητα.

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddDistributedMemoryCache(); // η κατάσταση συνεδρίας χρειάζεται μια κρυφή μνήμη (MVC/Razor Pages ήδη προσθέτουν μία)
builder.Services.AddSession(); // Η ασφάλεια εγγράφων Doconut βασίζεται στην κατάσταση συνεδρίας ASP.NET

app.UseSession();          // καλέστε το UseSession() πριν το UseDoconut()
app.UseDoconutResources(); // πρέπει να καταχωρηθεί πριν το UseDoconut()
app.UseDoconut();

Η εφαρμογή αναφοράς διατηρεί το ιστορικό σχήμα αιτήματος DocImage.axd και τοποθετεί το middleware εγγράφου σε έναν αντίστοιχο κλάδο. Αυτή είναι η διάταξη που πρέπει να αντιγράψετε όταν προχωράτε μια υπάρχουσα εφαρμογή .NET Standard, επειδή ο πελάτης συνεχίζει να καλεί το ίδιο URL που πάντα έκανε:

csharp
builder.Services.AddDoconut(options =>
{
    options.UnsafeMode      = false;   // διατηρήστε ενεργοποιημένο τον έλεγχο συνεδρίας του Doconut
    options.ShowDoconutInfo = false;
});
builder.Services.AddDistributedMemoryCache(); // η κατάσταση συνεδρίας χρειάζεται μια κρυφή μνήμη (MVC/Razor Pages ήδη προσθέτουν μία)
builder.Services.AddSession(options =>
{
    options.IdleTimeout        = TimeSpan.FromSeconds(3600);
    options.Cookie.HttpOnly    = true;
    options.Cookie.IsEssential = true;
});

// Απαιτείται για κωδικοποιήσεις πολλαπλών bytes σε ορισμένες μορφές εγγράφων.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseDoconutResources();   // ΠΡΙΝ το κλάδο του εγγράφου
app.MapWhen(
    context => context.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
    branch => branch.UseDoconut());

UseDoconut() δεν δέχεται επιλογές σε αυτήν την έκδοση — όλα έχουν μεταφερθεί στο AddDoconut(). Το MiddlewarePath είναι μια τιμή συντονισμού· δεν αντιστοιχίζει αυτόνομα έναν κλάδο ASP.NET Core. Το ResourcesPath εξυπηρετεί το ενσωματωμένο πακέτο στο /doconut-res, και η διαδρομή πόρων εικόνας του widget είναι επομένως ResPath: 'doconut-res/images'.

Προσθήκη του προβολέα σε μια σελίδα

Ο Viewer είναι ο απαιτούμενος πυρήνας της σελίδας. Η επιφάνεια απόδοσής του χρησιμοποιεί δύο ένθετα divs:

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

Αντιμετωπίστε τη γραμμή εργαλείων, τις τοποθετήσεις μονάδων και την επιφάνεια Viewer ως μία σύνθεση σελίδας. Η Αναζήτηση και το Σχόλιο ενσωματώνουν τις ενσωματωμένες ταινίες τους σε προαιρετικές τοποθετήσεις, αλλά αυτές οι μονάδες δεν λειτουργούν ποτέ αυτόνομα: συνδέονται πάντα με τον Viewer στην ίδια σελίδα.

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Πλοήγηση προβολέα, ζουμ, κουμπιά Αναζήτησης και Σχολίου -->
</nav>

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

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

Αναφορά των πόρων του Viewer

Σε μια Razor view, η ενσωματωμένη υπηρεσία Viewer εκδίδει τις ετικέτες <link> και <script> του προβολέα με σειρά εξαρτήσεων — το widget είναι ένα πρόσθετο jQuery, επομένως το jQuery πρέπει να φορτωθεί πριν από τα scripts του προβολέα:

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

Για το πλήρες πακέτο Viewer, ζητήστε μαζί τους πόρους του Viewer και των μονάδων:

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 και IncludeViewerScripts είναι οι υποχρεωτικές βασικές σημαίες. Ποτέ μην δημοσιεύετε ένα παράδειγμα Ribbon Αναζήτησης ή Σχολιασμού χωρίς αυτές, το mount του Viewer και μια παρουσία docViewer. ReferenceCss και ReferenceScripts παραλείπουν τους πόρους μιας προαιρετικής μονάδας όταν η τρέχουσα άδεια δεν παρέχει αυτή τη δυνατότητα· ο βασικός Viewer εξακολουθεί να ξεκινά.

Αυτές οι κλήσεις αντικαθιστούν τα χειροκίνητα αντιγραμμένα αρχεία script. Εάν η εφαρμογή εξακολουθεί να φορτώνει τα δικά της docViewer.js, documentLinks.js ή docViewer.UI.js, αφαιρέστε αυτές τις ετικέτες μόλις η σελίδα λειτουργεί από τους εκπεμπόμενους πόρους — δείτε τον οδηγός μετάπτωσης.

Αρχικοποίηση του Viewer

Το widget στην πλευρά του πελάτη είναι ένα πρόσθετο jQuery. Αυτό είναι ένα ελάχιστο σύνολο πραγματικών επιλογών εκκίνησης (όχι ψευδοκώδικας):

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 () {
        // οι σελίδες είναι ορατές· ασφαλές να κρύψετε έναν δείκτη φόρτωσης εδώ
    },
    // Προωθήστε τα γεγονότα κύκλου ζωής του σχολιασμού στο ενσωματωμένο ribbon όταν είναι παρόν.
    onAnnLoaded:    () => annBar?.handleAnnLoaded(),
    onAnnSaved:     () => annBar?.handleAnnSaved(),
    onAnnSaveError: () => annBar?.handleAnnSaveError(),
    onAnnClosed:    () => annBar?.handleAnnClosed(),
    onError: function (message) {
        console.error('Doconut viewer error:', message);
    }
});

Δύο λεπτομέρειες αποφασίζουν αν αυτή η σελίδα λειτουργεί καθόλου:

  • Οι callbacks είναι επιλογές. onViewerReady, onViewerBusy, onCopy και onAutoLoadStatus περνιούνται στο αντικείμενο επιλογών. Ο προηγούμενος Viewer αντλούσε ονόματα παγκόσμιων συναρτήσεων από το id του container και τις καλούσε· αυτός δεν το κάνει. Ένας χειριστής που δηλώνεται μόνο ως παγκόσμια συνάρτηση δεν εκτελείται ποτέ, και δεν αναφέρεται τίποτα.
  • Το ResPath δείχνει στο πρόθεμα των πόρων, όχι στη ρίζα της εφαρμογής. Με το προεπιλεγμένο ResourcesPath, αυτή η τιμή είναι doconut-res/images. Εάν αλλάξατε το ResourcesPath, χρησιμοποιήστε το δικό σας πρόθεμα.

Και τα δύο αποτυγχάνουν σιωπηρά — δείτε το Αντιμετώπιση προβλημάτων και τον οδηγό μετάπτωσης.

Η κεφαλοποίηση των επιλογών είναι πραγματικά μεικτή — showThumbs, autoLoad και pageZoom είναι camelCase, αλλά FitType, BasePath και ResPath είναι PascalCase. Δεν υπάρχει συνεπής κανόνας· αν κάνετε λάθος στην κεφαλοποίηση, η επιλογή αγνοείται σιωπηρά (το widget επιστρέφει στην προεπιλογή του αντί να πετάξει σφάλμα).

Συναρμολόγηση του πλήρους πακέτου Viewer

Και οι δύο εφαρμογές αναφοράς εγκαθιστούν τα παρακάτω μέρη μαζί σε μία σελίδα:

Μέρος του πακέτουΑπαίτησηΠώς συνδέεται
Πόροι Viewer, mount, και objViewerΑπαιτείταιΒασικός αποτυπωτής εγγράφου
Γραμμή εργαλείων ViewerΑπαιτείται στη σύνθεση αναφοράςΣήμανση κεντρικού υπολογιστή· τα κουμπιά καλούν το ίδιο objViewer
Ribbon ΑναζήτησηςΠροαιρετικό, αδειοδοτημένο μοντέλοdoconutSearchBar(...).attach(objViewer)
Ribbon ΣχολιασμούΠροαιρετικό, αδειοδοτημένο μοντέλοdoconutAnnotationBar(...).attach(objViewer)

Παρόλο που η κύρια γραμμή εργαλείων Viewer είναι σήμανση κεντρικού υπολογιστή, εγκαθίσταται παράλληλα με το Viewer και δεν πρέπει ποτέ να τεκμηριώνεται ως απομονωμένος έλεγχος. Αυτό διατηρεί τη διάταξη, τις ετικέτες, τα εικονίδια και τους κανόνες εξουσιοδότησης υπό τον έλεγχο της εφαρμογής σας, ενώ κάθε κουμπί οδηγεί στην ίδια παρουσία 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>

Διατηρήστε τη σειρά αρχικοποίησης του πακέτου που χρησιμοποιούν και οι δύο εφαρμογές αναφοράς:

  1. Εκπομπή CSS για το Viewer και τις αδειοδοτημένες μονάδες.
  2. Απόδοση της γραμμής εργαλείων Viewer, των mounts Αναζήτησης/Σχολιασμού και του mount Viewer μαζί.
  3. Εκπομπή scripts για το Viewer και τις αδειοδοτημένες μονάδες.
  4. Φόρτωση του δικού σας script βοηθού γραμμής εργαλείων της εφαρμογής κεντρικού υπολογιστή.
  5. Αρχικοποίηση του docViewer και διατήρηση του προκύπτοντος objViewer.
  6. Αρχικοποίηση κάθε αδειοδοτημένου Ribbon Αναζήτησης ή Σχολιασμού.
  7. Κλήση attach(objViewer) σε κάθε Ribbon.
  8. Άνοιγμα του εγγράφου και διατήρηση του token του για αιτήματα Viewer και μονάδων.

Οι προστασίες στην πλευρά του διακομιστή είναι σημαντικές: όταν μια προαιρετική δυνατότητα δεν είναι διαθέσιμη, το script της δεν εκπέμπεται, έτσι η συνάρτηση plug‑in του jQuery δεν υπάρχει.

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>

Και τα δύο ενσωματωμένα στοιχεία δημιουργούν το δικό τους DOM Ribbon. Το Search περιλαμβάνει ομάδες Find, Options και Results. Το Annotation περιλαμβάνει τα εργαλεία συγγραφής, ελέγχους στυλ, ενέργειες αποθήκευσης και προαιρετικές ενέργειες εξαγωγής/εικόνας. Οι μπαρ εκθέτουν open(), close(), reset() και isOpen()· καλείτε πάντα attach(objViewer) μία φορά μετά τη δημιουργία τους.

Το παραπάνω παράδειγμα παραλείπει προαιρετικά callbacks κεντρικού υπολογιστή και τελικά σημεία εξαγωγής/εικόνας του Annotation για να κρατήσει την εκκίνηση ελάχιστη. Δείτε το Search και τις Annotations για τη πλήρη ρύθμιση ανά χαρακτηριστικό, ή τα Custom Themes για να μορφοποιήσετε ή να αντικαταστήσετε τη γραμμή εργαλείων Viewer που ανήκει στον κεντρικό υπολογιστή.

Άνοιγμα εγγράφου

Η πλευρά του διακομιστή είναι ένα σημείο λήψης: η ενσωματωμένη υπηρεσία Viewer ανοίγει το έγγραφο και επιστρέφει ένα διακριτικό συνεδρίας. Το άνοιγμα είναι ασύγχρονο σε αυτήν την έκδοση.

csharp
app.MapPost("/api/open", async (Viewer viewer) =>
{
    // Το διακριτικό είναι αδιαφανές — δώστε το στο widget, μην το καταγράφετε ή το αποθηκεύετε.
    string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
    return Results.Ok(new { token });
});

Ο πελάτης λαμβάνει αυτό το διακριτικό και το δίνει στο widget με objViewer.View(token):

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

Δεν υπάρχει τίποτα για προθέρμανση πριν το πρώτο άνοιγμα. Εάν προχωράτε με μια εφαρμογή, διαγράψτε οποιαδήποτε κλήση InitCache() και τη σημαία που την προστατεύει — η μέθοδος δεν υπάρχει πλέον.

Κλείσιμο του εγγράφου

Καλέστε objViewer.Close() όταν ο χρήστης αφήνει το viewer ή ανοίγει ένα αντικαταστάσιμο έγγραφο. Σε ροές εργασίας που ελέγχονται από τον διακομιστή, το viewer.CloseDocument(token) αφαιρεί αμέσως τη cached συνεδρία, απελευθερώνει τη μηχανή απόδοσης, διαγράφει το σήμα ασφαλείας της και ανακαλεί το διακριτικό. Η κυλιόμενη λήξη τελικά εκτελεί τον ίδιο καθαρισμό, αλλά συνιστάται ρητό κλείσιμο για μεγάλα έγγραφα.

Η ολοκληρωμένη ροή αιτήματος είναι:

text
AddDoconut + middleware
    -> render CSS/scripts and mount div
    -> initialize docViewer
    -> OpenDocumentAsync
    -> return opaque token
    -> objViewer.View(token)
    -> page/search/annotation requests
    -> Close / CloseDocument

Αντιμετωπίστε το διακριτικό όπως ένα διαπιστευτήριο τύπου bearer: μην το καταγράφετε ποτέ, μην το αποθηκεύετε ποτέ, δώστε το μόνο στο widget. Αναγνωρίζει μια ενεργή συνεδρία εγγράφου στον διακομιστή και σταματά να λειτουργεί όταν η συνεδρία λήξει — ανοίξτε ξανά το έγγραφο για να λάβετε ένα νέο.

Εκτέλεση

Τοποθετήστε ένα PDF στο wwwroot/files/Sample.pdf, εκτελέστε dotnet run και ανοίξτε τη σελίδα που φιλοξενεί το widget. Η πρώτη σελίδα αποδίδεται στο viewer, με ένα πάνελ μικρογραφιών στα αριστερά. Εάν δεν συμβεί, δείτε Αντιμετώπιση προβλημάτων.

Τι λαμβάνετε χωρίς άδεια

Η έλλειψη άδειας δεν προκαλεί σφάλμα. Το viewer αποδίδει κανονικά, αλλά κάθε σελίδα φέρει ένα υδατογράφημα αξιολόγησης. Δείτε Ρύθμιση άδειας για το πώς το Doconut βρίσκει μια άδεια και τι αλλάζει όταν το κάνει.

Ήταν αυτή η σελίδα χρήσιμη;