Introduzione

Un collega invia due revisioni di un contratto e ti chiede di confrontarle. Inserisci entrambi nel tuo servizio di confronto, il risultato ritorna e tutto sembra normale. Ciò che non hai visto è che uno di quei documenti conteneva un’immagine collegata che puntava a un URL, e il tuo server ha contattato quell’host nel momento in cui il file è stato aperto. Niente nell’output ti avvisa che è avvenuto.

Questo non è un difetto – è ciò che significa caricare fedelmente un documento. Un file OOXML può riferire un’immagine che vive su un server web anziché all’interno del pacchetto, e sia Word sia qualsiasi libreria che carica correttamente il documento risolvono quel riferimento. GroupDocs.Comparison per .NET espone due proprietà su LoadOptions che ti permettono di decidere se farlo: SkipExternalResources e WhitelistedResources.

Insieme forniscono tre configurazioni, e questo articolo confronta tutte e tre – il predefinito permissivo, il blocco totale e il blocco totale eccetto i riferimenti nominati. Alla fine saprai quale scegliere per una data origine del documento, e i due errori che fanno sembrare che queste impostazioni non funzionino.

💡 Esempio completo funzionante: block-external-resources-on-document-load-dotnet – un progetto console eseguibile che serve le immagini referenziate stesso e registra ogni richiesta, così puoi osservare l’effetto di ciascuna impostazione.

Dove si Nascondono i Riferimenti Esterni

Prima di scegliere un’impostazione, è utile capire cosa si sta scegliendo. Un file .docx contiene riferimenti esterni in due luoghi distinti, ed è facile perderli di vista perché nessuno è visibile nel testo del documento.

Il primo è una relazione in word/_rels/document.xml.rels che contiene TargetMode="External" e un URL assoluto. L’immagine appare nel corpo come un disegno che punta alla relazione tramite ID, quindi l’URL stesso non compare vicino al contenuto che influenza.

Il secondo è un codice campo INCLUDEPICTURE nel corpo del documento, che contiene il suo URL all’interno di un’istruzione di campo. Word lo risolve quando la pagina viene renderizzata; una libreria di confronto lo risolve quando il documento viene caricato.

Entrambi i meccanismi rispettano le due opzioni di caricamento discusse di seguito, il che è importante perché un documento può usare uno o entrambi. Un riferimento che hai individuato nel file delle relazioni non è prova che non ne esista un secondo in un codice campo.

Approccio 1: Il Predefinito – Riferimenti Risolti

SkipExternalResources è impostato di default a false, quindi un documento caricato senza configurazione ha i suoi riferimenti remoti risolti:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = false
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

Questo garantisce la massima fedeltà: i documenti confrontati contengono tutto ciò a cui fanno riferimento, esattamente come Word li visualizzerebbe. Per i documenti prodotti dalla tua stessa applicazione o dai template, dove ogni URL di riferimento punta a un’infrastruttura che gestisci, è la scelta giusta – e un’immagine collegata mancante potrebbe rendere il confronto fuorviante.

Il costo è che ogni riferimento viene contattato, chiunque lo abbia inserito. C’è anche un costo di tempistica che non ha nulla a che fare con la fiducia: un URL di riferimento che non risolve più fa attendere il caricamento per l’intero tentativo di connessione, ad ogni singolo confronto.

Approccio 2: Bloccare Ogni Risorsa Esterna

Una proprietà disattiva la risoluzione dei riferimenti remoti per quel documento:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = true
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

Nessuna richiesta viene inviata. Le immagini referenziate sono assenti dal risultato, e – questo è il punto da chiarire – nient’altro cambia. L’impostazione regola ciò che viene caricato, non come vengono trovate le differenze, quindi le modifiche testuali e strutturali tra i due documenti vengono rilevate esattamente come prima. L’unica cosa che perdi è la possibilità di rilevare una modifica all’interno di un’immagine referenziata, che non è mai stata caricata.

Questa è la configurazione da considerare come baseline per i documenti che non hai creato: caricamenti da parte di utenti in un’app web, file ricevuti via email, qualsiasi cosa confrontata su un agente di build dove una richiesta in uscita è raramente voluta. È tutto o niente, però – un’immagine collegata che realmente volevi è bloccata insieme al resto, e il risultato semplicemente non la contiene senza annunciare il motivo.

Approccio 3: Bloccare Tutto Tranne i Riferimenti Nominati

La terza configurazione è quella che premia una lettura attenta. WhitelistedResources accetta una List<string> ed è consultata solo quando SkipExternalResources è true:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = true,
    WhitelistedResources = new List<string> { "includepicture-field.png" }
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

Le voci sono frammenti di URL, non nomi di file. Ognuno è confrontato con l’URL di riferimento, e una corrispondenza in qualsiasi punto lo ammette. Questo è ciò che rende la whitelist portabile: "includepicture-field.png" ammette l’immagine indipendentemente dallo schema, host e percorso che lo precedono, così la stessa lista funziona in sviluppo e produzione senza riscritture.

La stessa proprietà può funzionare al contrario. Un frammento corto o generico – logo.png, o peggio, .png – può corrispondere a riferimenti che non intendevi consentire. Scegli un frammento sufficientemente specifico da identificare la singola risorsa che desideri.

Nel campione di riferimento, questa configurazione recupera l’immagine nella whitelist e lascia bloccata una seconda immagine referenziata, per la quale nessuna voce è presente. Il registro delle richieste mostra tre richieste dove il predefinito permissivo ne produceva cinque, e nomina solo il file nella whitelist.

Quale Configurazione Dovresti Usare?

Abbina l’impostazione all’origine del documento. I documenti generati dalla tua applicazione o dai tuoi template possono mantenere il predefinito, perché ogni URL di riferimento punta a un’infrastruttura che già gestisci. Qualsiasi cosa provenga dall’esterno – caricamenti da parte degli utenti, allegati email, file di terze parti – richiede SkipExternalResources = true. Aggiungi un frammento ristretto in WhitelistedResources solo quando un riferimento fidato deve davvero essere risolto.

Confronto delle Tre Configurazioni

Aspetto Predefinito Blocca tutto Blocca + whitelist
Proprietà da impostare 0 1 2
Richieste in uscita tutti i riferimenti nessuna solo quelli nella whitelist
Controllo per riferimento no no sì
URL non validi aumentano il tempo di caricamento sì no solo quelli nella whitelist
Ideale per documenti prodotti da te documenti provenienti da fonti esterne template fidati tra contenuti non fidati

La decisione segue la provenienza del documento più che le prestazioni. I documenti generati dal tuo sistema possono mantenere il predefinito. I documenti provenienti dall’esterno richiedono il blocco. Usa la whitelist solo quando un riferimento specifico deve davvero essere risolto – ad esempio un template aziendale che preleva l’immagine dell’intestazione da un URL interno, tra report i cui autori hanno inserito immagini da qualsiasi fonte.

I Due Errori

Entrambi gli errori producono lo stesso sintomo: imposti l’opzione e sembra non fare nulla.

Una whitelist senza l’interruttore. WhitelistedResources è consultata solo quando SkipExternalResources è true. Se impostata da sola, non ha alcun effetto – non c’è blocco a cui fare eccezione. Se una whitelist sembra ignorata, verifica prima questo punto.

Opzioni solo sulla sorgente. Questo è più subdolo. Le opzioni di caricamento descrivono come un documento viene caricato. Il costruttore di Comparer accetta le opzioni per la sorgente; ogni chiamata a Add() accetta le opzioni per quel target:

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

Se le passi al costruttore e dimentichi di passarle a Add(), la sorgente è protetta mentre ogni target continua a recuperare i propri riferimenti. Il confronto ha successo, il risultato sembra plausibile, e metà dei tuoi documenti continua a contattare la rete. Quando sorgente e target richiedono gestioni diverse, passa istanze separate di LoadOptions – è precisamente per questo che l’API le accetta per documento.

Verificare che Abbia Funzionato

Una risorsa bloccata lascia quasi nessuna traccia. Il documento di output manca di un’immagine, cosa che assomiglia molto a un documento che non ne ha mai avuto. Leggere il file di risultato è quindi un modo poco affidabile per confermare che l’impostazione abbia avuto effetto.

Osserva invece il lato server. Il campione di riferimento adotta deliberatamente questo approccio: avvia un piccolo listener HTTP su una porta di loopback libera, scrive i documenti demo puntando a quella porta e registra ogni richiesta ricevuta, stampando il conteggio per confronto. Cinque richieste, poi zero, poi tre. Un tracciamento di rete contro le tue fonti di documento reali ti dà la stessa certezza.

Conclusione

Tre configurazioni, una regola decisionale: lascia che i documenti che hai generato mantengano il predefinito, imposta SkipExternalResources = true per tutto il resto, e aggiungi una whitelist con un frammento URL ristretto solo dove un riferimento fidato specifico deve ancora risolversi.

Poi controlla le due cose che annullano silenziosamente il lavoro – una whitelist senza SkipExternalResources = true e opzioni passate al costruttore di Comparer ma non a ogni chiamata Add() – e verifica dal lato server anziché dal file di output.

Risorse Aggiuntive