Γρήγορη Εκκίνηση
Αποδώστε το πρώτο σας έγγραφο σε λίγα λεπτά
Αυτός ο οδηγός μετατρέπει μια εφαρμογή ASP.NET Core από ένα κενό Program.cs σε ένα έγγραφο που αποδίδεται στον περιηγητή: εγγραφή του διακομιστή, το πλήρες πακέτο Viewer (γραμμή εργαλείων Viewer, τοποθέτηση Viewer και προαιρετικές ταινίες Αναζήτησης/Σχόλιου), αναφορές πόρων, αρχικοποίηση πελάτη, άνοιγμα εγγράφου και εκτέλεση.
Ρύθμιση διακομιστή
AddDoconut() καταχωρεί τις υπηρεσίες· UseDoconutResources() και UseDoconut() συνδέουν το middleware. Η κλήση των πόρων πρέπει να γίνει πρώτα. Οι κλήσεις συνεδρίας είναι επίσης απαραίτητες — η προεπιλεγμένη ασφάλεια εγγράφων του Doconut επαληθεύει κάθε αίτημα σελίδας έναντι της κατάστασης της συνεδρίας ASP.NET. Η κατάσταση της συνεδρίας χρειάζεται μια κρυφή μνήμη: το AddDistributedMemoryCache() παρέχει μία. Μια εφαρμογή MVC ή Razor Pages την καταχωρεί ήδη, αλλά μια εφαρμογή minimal‑API δεν το κάνει, και χωρίς αυτή η εφαρμογή σταματά κατά την εκκίνηση με το σφάλμα Unable to resolve service for type 'IDistributedCache'. Έχετε ήδη καταχωρήσει το Doconut κατά την Εγκατάσταση; προχωρήστε στην επόμενη ενότητα.
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();Για μια διάταξη διαδρομών τύπου παραγωγής, αντιστοιχίστε το middleware εγγράφου σε έναν ρητό κλάδο και διατηρήστε τις τέσσερις ρυθμίσεις διαδρομής ευθυγραμμισμένες:
builder.Services.AddDoconut(options =>
{
options.LicensePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", "Doconut.Viewer.lic");
options.MiddlewarePath = "/doconut";
options.ResourcesPath = "/doconut-res";
options.UnsafeMode = false;
});
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
app.UseRouting();
app.UseSession();
app.UseDoconutResources();
app.Map("/doconut", branch => branch.UseDoconut());MiddlewarePath είναι μια τιμή συντονισμού· δεν αντιστοιχίζει έναν κλάδο ASP.NET Core από μόνη της. Σε αυτό το παράδειγμα ο κεντρικός υπολογιστής αντιστοιχίζει το /doconut, έτσι ο πελάτης χρησιμοποιεί BasePath: 'doconut'. Το ResourcesPath εξυπηρετεί το ενσωματωμένο πακέτο στο /doconut-res, και η διαδρομή πόρων εικόνας του widget είναι επομένως ResPath: 'doconut-res/images'. Γράψτε και τα δύο χωρίς αρχική κάθετο: ο προβολέας προσθέτει μία μόνο του, έτσι το '/doconut' θα γίνει //doconut/DocImage.axd, το οποίο ο περιηγητής ερμηνεύει ως αίτημα σε έναν κεντρικό υπολογιστή με όνομα doconut, και ο προβολέας αποτυγχάνει με το μήνυμα «The viewer handler could not be reached».
Προσθήκη του προβολέα σε μια σελίδα
Ο Viewer είναι ο απαιτούμενος πυρήνας της σελίδας. Η επιφάνεια απόδοσής του χρησιμοποιεί δύο ένθετα divs:
<div id="divDocViewer">
<div id="div_ctlDoc"></div>
</div>Αντιμετωπίστε τη γραμμή εργαλείων, τις τοποθετήσεις μονάδων και την επιφάνεια του Viewer ως μία σύνθεση σελίδας. Η Αναζήτηση και το Σχόλιο ενσωματώνουν τις ταινίες τους σε προαιρετικές τοποθετήσεις, αλλά αυτές οι μονάδες δεν λειτουργούν ποτέ ανεξάρτητα: συνδέονται πάντα με τον Viewer στην ίδια σελίδα. Χρησιμοποιήστε την ίδια σειρά όπως στα Doconut.TestApp και Doconut.TestApp.Distributed:
<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>Αναφορά στα περιουσιακά στοιχεία του προβολέα
Σε μια Razor view, η ενσωματωμένη υπηρεσία Viewer εκδίδει τις ετικέτες <link> και <script> του προβολέα με σειρά εξάρτησης — το widget είναι ένα πρόσθετο jQuery, έτσι το jQuery πρέπει να φορτωθεί πριν τα σενάρια του προβολέα:
@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.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 είναι οι υποχρεωτικές σημαίες πυρήνα. Ποτέ μην δημοσιεύετε ένα παράδειγμα ταινίας Αναζήτησης ή Σχόλιου χωρίς αυτές, χωρίς την τοποθέτηση Viewer και χωρίς μια παρουσίαση docViewer. Τα ReferenceCss και ReferenceScripts παραλείπουν τους πόρους μιας προαιρετικής μονάδας όταν η τρέχουσα άδεια δεν παρέχει αυτή τη δυνατότητα· ο πυρήνας Viewer εξακολουθεί να εκκινείται.
Αρχικοποίηση του προβολέα
Το widget στην πλευρά του πελάτη είναι ένα πρόσθετο jQuery. Αυτό είναι ένα ελάχιστο σύνολο πραγματικών επιλογών αρχικοποίησης (όχι ψευδοκώδικας):
let searchBar = null;
let annBar = null;
const objViewer = $('#div_ctlDoc').docViewer({
showThumbs: true,
autoLoad: false,
pageZoom: 100,
FitType: 'width',
BasePath: 'doconut', // χωρίς αρχικό '/'
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);
}
});Η κεφαλαιοποίηση των επιλογών είναι πραγματικά ανάμεικτη — showThumbs, autoLoad και pageZoom είναι camelCase, αλλά FitType, BasePath και ResPath είναι PascalCase. Δεν υπάρχει συνεπής κανόνας· αν κάνετε λάθος στην κεφαλαιοποίηση, η επιλογή αγνοείται σιωπηρά (το widget επανέρχεται στην προεπιλογή του αντί να πετάξει σφάλμα).
Συναρμολόγηση του πλήρους πακέτου Viewer
Και οι δύο εφαρμογές αναφοράς .NET 8 εγκαθιστούν τα ακόλουθα μέρη μαζί σε μία σελίδα:
| Μέρος του πακέτου | Απαίτηση | Πώς συνδέεται |
|---|---|---|
Πόροι Viewer, mount και objViewer | Απαιτείται | Core document renderer |
| Γραμμή εργαλείων Viewer | Απαιτείται στη σύνθεση αναφοράς | Host markup; buttons call the same objViewer |
| Ribbon αναζήτησης | Προαιρετικό, αδειοδοτημένο μοντέλο | doconutSearchBar(...).attach(objViewer) |
| Ribbon σχολιασμού | Προαιρετικό, αδειοδοτημένο μοντέλο | doconutAnnotationBar(...).attach(objViewer) |
Παρόλο που η κύρια γραμμή εργαλείων Viewer είναι σήμανση host, εγκαθίσταται παράλληλα με το Viewer και δεν πρέπει ποτέ να τεκμηριώνεται ως ανεξάρτητος έλεγχος. Αυτό διατηρεί τη διάταξη, τις ετικέτες, τα εικονίδια και τους κανόνες εξουσιοδότησης υπό τον έλεγχο της εφαρμογής σας, ενώ κάθε κουμπί ελέγχει την ίδια παρουσία Viewer:
<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>Η πλήρης γραμμή εργαλείων αναφοράς αντιγράφει επίσης το wwwroot/js/viewerToolbar.js στην εφαρμογή host για περιστροφή, μικρογραφίες, εκτύπωση, πλήρη οθόνη, διάταξη και βοηθητικά εργαλεία κατάστασης κουμπιών. Φορτώστε αυτό το αρχείο host μετά το Viewer.ReferenceScripts(...). Διατηρήστε το βοηθητικό πρόγραμμα και τη σήμανση <nav id="toolbar"> μαζί όταν αντιγράφετε την πλήρη υλοποίηση demo.
Διατηρήστε τη σειρά αρχικοποίησης του πακέτου που χρησιμοποιούν και οι δύο εφαρμογές αναφοράς:
- Εκπονήστε τους πόρους Viewer, Search και Annotation μαζί.
- Αποδώστε τη γραμμή εργαλείων Viewer, τα mounts του Ribbon και το mount του Viewer μαζί.
- Αρχικοποιήστε πρώτα το
docViewer. - Δημιουργήστε κάθε αδειοδοτημένο Ribbon και συνδέστε το με το ίδιο
objViewer. - Ανοίξτε το έγγραφο και διατηρήστε το token του για αιτήματα μονάδας.
Doconut.TestApp.Distributed διατηρεί αυτήν την ακριβή σύνθεση UI και το ίδιο βοηθητικό πρόγραμμα Viewer-toolbar. Η πρόσθετη τιμή αίτησης access και οι ρυθμίσεις επανάληψης ασύγχρονης απόδοσης ανήκουν στη διανεμημένη μεταφορά· δεν αλλάζουν τον τρόπο συναρμολόγησης του Viewer, της γραμμής εργαλείων ή των Ribbons.
Οι έλεγχοι στην πλευρά του διακομιστή είναι σημαντικοί: όταν μια προαιρετική δυνατότητα δεν είναι διαθέσιμη, το script της δεν εκδίδεται, έτσι η λειτουργία plug-in του jQuery δεν υπάρχει.
<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) μία φορά μετά τη δημιουργία τους.
Το παραπάνω παράδειγμα παραλείπει προαιρετικές κλήσεις host και τα σημεία εξόδου εξαγωγής/εικόνας του Annotation για να διατηρήσει την εκκίνηση ελάχιστη. Δείτε το Αναζήτηση και το Σχόλια για τη πλήρη ρύθμιση συγκεκριμένων λειτουργιών, ή το Προσαρμοσμένα Θέματα για να μορφοποιήσετε ή να αντικαταστήσετε τη γραμμή εργαλείων Viewer που ανήκει στο host.
Άνοιγμα εγγράφου
Η πλευρά του διακομιστή είναι ένα endpoint: η ενσωματωμένη υπηρεσία Viewer ανοίγει το έγγραφο και επιστρέφει ένα token συνεδρίας.
app.MapPost("/api/open", async (Viewer viewer) =>
{
// Το token είναι αδιαφανές — παραδώστε το στο widget, ποτέ μην το καταγράψετε ή το αποθηκεύσετε.
string token = await viewer.OpenDocumentAsync("wwwroot/files/Sample.pdf");
return Results.Ok(new { token });
});Ο πελάτης λαμβάνει αυτό το token και το παραδίδει στο widget με objViewer.View(token):
fetch('/api/open', { method: 'POST' })
.then(resp => resp.json())
.then(data => {
currentToken = data.token;
objViewer.View(currentToken);
});Κλείσιμο του εγγράφου
Κλήση του objViewer.Close() όταν ο χρήστης αφήνει το viewer ή ανοίγει ένα αντικαταστάτη έγγραφο. Σε ροές εργασίας που ελέγχονται από τον διακομιστή, το viewer.CloseDocument(token) αφαιρεί αμέσως τη λανθάνουσα συνεδρία, απελευθερώνει τη μηχανή απόδοσης, διαγράφει το σήμα ασφαλείας της και ανακαλεί το token. Η ολίσθηση λήξης τελικά εκτελεί τον ίδιο καθαρισμό, αλλά συνιστάται ρητό κλείσιμο για μεγάλα έγγραφα.
Η ολοκληρωμένη ροή αιτήματος είναι:
AddDoconut + middleware
-> render CSS/scripts and mount div
-> initialize docViewer
-> OpenDocumentAsync
-> return opaque token
-> objViewer.View(token)
-> page/search/annotation requests
-> Close / CloseDocumentΑντιμετωπίστε το token σαν διαπιστευτήριο τύπου bearer: ποτέ μην το καταγράψετε, ποτέ μην το αποθηκεύσετε, παραδώστε το μόνο στο widget. Αναγνωρίζει μια ζωντανή συνεδρία εγγράφου στον διακομιστή και σταματά να λειτουργεί όταν αυτή η συνεδρία λήξει — ανοίξτε ξανά το έγγραφο για να λάβετε ένα νέο.
Εκτέλεση
Τοποθετήστε ένα PDF στο wwwroot/files/Sample.pdf, τρέξτε dotnet run και ανοίξτε τη σελίδα που φιλοξενεί το widget. Η πρώτη σελίδα αποδίδεται στο viewer, με ένα πάνελ μικρογραφιών στα αριστερά. Αν δεν συμβαίνει, δείτε Troubleshooting.
Τι παίρνετε χωρίς άδεια
Μια ελλιπής άδεια δεν προκαλεί εξαίρεση. Το viewer αποδίδει κανονικά, αλλά κάθε σελίδα φέρει υδατογράφημα αξιολόγησης. Δείτε License Setup για το πώς το Doconut βρίσκει μια άδεια και τι αλλάζει όταν το κάνει.
Ήταν αυτή η σελίδα χρήσιμη;