Introductie

Een collega stuurt twee revisies van een contract en vraagt je ze te vergelijken. Je plaatst beide in je vergelijkingsservice, het resultaat komt terug en alles ziet er normaal uit. Wat je niet zag, is dat één van die documenten een gekoppelde afbeelding bevatte die naar een URL verwees, en je server dat host contacteerde op het moment dat het bestand werd geopend. Niets in de output vertelt je dat dit is gebeurd.

Dit is geen defect – het is wat het laden van een document getrouw betekent. Een OOXML‑bestand kan een afbeelding refereren die op een webserver staat in plaats van in het pakket, en zowel Word als elke bibliotheek die het document correct laadt, lossen die referentie op. GroupDocs.Comparison for .NET biedt twee eigenschappen op LoadOptions die je laten bepalen of dit gebeurt: SkipExternalResources en WhitelistedResources.

Samen geven ze drie configuraties, en dit artikel vergelijkt alle drie – de permissieve standaard, alles blokkeren, en alles blokkeren behalve benoemde referenties. Aan het einde weet je welke je moet kiezen voor een bepaalde documentbron, en de twee fouten die ervoor zorgen dat deze instellingen lijken te werken alsof ze niets doen.

💡 Volledig werkend voorbeeld: block-external-resources-on-document-load-dotnet – een uitvoerbaar console‑project dat de verwezen afbeeldingen zelf serveert en elke aanvraag logt, zodat je elke instelling in werking kunt zien.

Waar externe referenties zich verbergen

Voordat je een instelling kiest, is het de moeite waard te weten wat je precies kiest. Een .docx bevat externe referenties op twee verschillende plaatsen, en ze zijn gemakkelijk te missen omdat geen van beide zichtbaar is in de documenttekst.

De eerste is een relatie in word/_rels/document.xml.rels met TargetMode="External" en een absolute URL. De afbeelding verschijnt in de body als een tekening die naar de relatie verwijst via een ID, zodat de URL zelf nooit naast de inhoud die hij beïnvloedt verschijnt.

De tweede is een INCLUDEPICTURE‑veldcode in de documentbody, die zijn URL bevat binnen een veldinstructie. Word lost dit op wanneer de pagina wordt gerenderd; een vergelijkingsbibliotheek lost het op wanneer het document wordt geladen.

Beide mechanismen respecteren de twee laadopties die hieronder worden besproken, wat van belang is omdat een document één of beide kan gebruiken. Een referentie die je in het relaties‑bestand hebt gezien, bewijst niet dat er geen tweede referentie in een veldcode bestaat.

Aanpak 1: De standaard – referenties worden opgelost

SkipExternalResources staat standaard op false, dus een document dat zonder configuratie wordt geladen, heeft zijn externe referenties opgelost:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = false
};

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

Dit levert de hoogste getrouwheid: de te vergelijken documenten bevatten alles waarnaar ze verwijzen, precies zoals Word ze zou weergeven. Voor documenten die je eigen applicatie of sjablonen hebben geproduceerd, waarbij elke referentie‑URL naar infrastructuur wijst die je beheert, is dit de juiste keuze – en een ontbrekende gekoppelde afbeelding kan de vergelijking actief misleidend maken.

Het nadeel is dat elke referentie wordt benaderd, wie die ook heeft geplaatst. Er is ook een tijds‑kost die niets met vertrouwen te maken heeft: een referentie‑URL die niet meer oplost, laat het laden wachten tot de volledige verbindingspoging is voltooid, bij elke enkele vergelijking.

Aanpak 2: Alles blokkeren

Eén eigenschap schakelt de resolutie van externe referenties uit voor dat document:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = true
};

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

Er wordt geen verzoek verzonden. De verwezen afbeeldingen ontbreken in het resultaat, en – dit is het punt dat duidelijk moet worden gemaakt – er verandert verder niets. De instelling bepaalt wat er wordt geladen, niet hoe verschillen worden gevonden, dus tekstuele en structurele wijzigingen tussen de twee documenten worden nog steeds precies zoals voorheen gedetecteerd. Het enige wat je verliest, is de mogelijkheid om een wijziging in een verwezen afbeelding te detecteren, omdat die nooit werd geladen.

Dit is de configuratie die je als basislijn moet beschouwen voor documenten die je niet zelf hebt gemaakt: gebruikers‑uploads in een webapplicatie, bestanden die per e‑mail zijn ontvangen, alles dat op een build‑agent wordt vergeleken waar een uitgaand verzoek zelden de bedoeling is. Het is alles‑of‑niets, echter – een gekoppelde afbeelding die je wel wilt, wordt geblokkeerd samen met de rest, en het resultaat mist die afbeelding zonder dit te melden.

Aanpak 3: Alles blokkeren behalve benoemde referenties

De derde configuratie is degene die een zorgvuldige lezing beloont. WhitelistedResources neemt een List<string> en wordt alleen geraadpleegd wanneer SkipExternalResources true is:

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

De items zijn URL‑fragmenten, geen bestandsnamen. Elk fragment wordt vergeleken met de referentie‑URL, en een overeenkomst ergens in die URL staat de bron toe. Dat maakt de whitelist draagbaar: "includepicture-field.png" staat de afbeelding toe ongeacht welk schema, host en pad ervoor staan, zodat dezelfde lijst in ontwikkeling en productie werkt zonder herschrijven.

Dezelfde eigenschap werkt in de andere richting. Een kort of generiek fragment – logo.png, of nog erger, .png – kan referenties matchen die je nooit had willen toestaan. Kies een fragment dat specifiek genoeg is om de ene bron die je bedoelt te identificeren.

In het referentie‑voorbeeld haalt deze configuratie de witgelijste afbeelding op en laat een tweede verwezen afbeelding, waarvoor geen entry bestaat, geblokkeerd. Het aanvraaglogboek toont drie verzoeken waar de permissieve standaard er vijf produceerde, en noemt alleen het witgelijste bestand.

Welke configuratie moet je gebruiken?

Stem de instelling af op de herkomst van het document. Documenten die je eigen applicatie of sjablonen hebben gegenereerd, kun je de standaard laten behouden, omdat elke referentie‑URL naar infrastructuur wijst die je al beheert. Alles wat van buitenaf komt – gebruikers‑uploads, e‑mailbijlagen, bestanden van derden – rechtvaardigt SkipExternalResources = true. Voeg een smal WhitelistedResources‑fragment alleen toe wanneer één vertrouwde referentie echt moet worden opgelost.

De drie configuraties vergelijken

Aspect Standaard Alles blokkeren Blokkeren + whitelist
Te zetten eigenschappen 0 1 2
Uitgaande verzoeken alle referenties geen alleen witgelijst
Controle per referentie nee nee ja
Dode URL kost laadtijd ja nee alleen witgelijst
Ideaal voor documenten die jij hebt geproduceerd documenten van elders vertrouwde sjablonen tussen onbetrouwbare inhoud

De beslissing volgt de herkomst van het document eerder dan prestaties. Documenten die door jouw systemen zijn gegenereerd, kunnen de standaard behouden. Documenten van buitenaf moeten geblokkeerd worden. Whitelist alleen op het punt waar één specifieke referentie echt moet worden opgelost – bijvoorbeeld een bedrijfs‑sjabloon dat zijn header‑afbeelding van een interne URL haalt, te midden van rapporten waarvan de auteurs afbeeldingen van willekeurige bronnen hebben geplakt.

De twee fouten

Beide fouten veroorzaken hetzelfde symptoom: je stelt de optie in, en het lijkt niets te doen.

Een whitelist zonder de schakelaar. WhitelistedResources wordt alleen geraadpleegd wanneer SkipExternalResources true is. Alleen ingesteld doet het helemaal niets – er is geen blokkering waarvoor een uitzondering kan worden gemaakt. Als een whitelist genegeerd lijkt, controleer dit dan eerst.

Opties alleen op de bron. Dit is de subtielere fout. Laadopties beschrijven hoe één document wordt geladen. De Comparer‑constructor neemt de opties voor de bron; elke Add()‑aanroep neemt de opties voor dat doel:

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

Geef ze door aan de constructor en vergeet de Add()‑aanroep, dan is de bron beschermd terwijl elk doel nog steeds zijn referenties ophaalt. De vergelijking slaagt, het resultaat lijkt plausibel, en de helft van je documenten blijft nog steeds netwerken benaderen. Waar bron en doel verschillende behandeling nodig hebben, geef je aparte LoadOptions‑instanties door – dat is precies waarom de API ze per document accepteert.

Verifiëren dat het daadwerkelijk werkt

Een geblokkeerde bron laat bijna geen spoor achter. Het uitvoerdocument mist een afbeelding, wat sterk lijkt op een document dat nooit een afbeelding had. Het lezen van het resultaatbestand is daarom een slechte manier om te bevestigen dat de instelling effect heeft gehad.

Bekijk in plaats daarvan de serverkant. Het referentie‑voorbeeld kiest bewust deze aanpak: het start een kleine HTTP‑listener op een vrije loopback‑poort, schrijft zijn demodocumenten die naar die poort wijzen, en logt elk verzoek dat het ontvangt, waarbij het aantal per vergelijking wordt afgedrukt. Vijf verzoeken, dan nul, dan drie. Een netwerk‑trace tegen je echte documentbronnen geeft je dezelfde zekerheid.

Conclusie

Drie configuraties, één beslissingsregel: laat documenten die jij hebt gegenereerd de standaard behouden, stel SkipExternalResources = true in voor alles andere, en whitelist een smal URL‑fragment alleen waar een specifieke vertrouwde referentie nog moet worden opgelost.

Controleer vervolgens de twee zaken die stilletjes het werk ongedaan maken – een whitelist zonder SkipExternalResources = true, en opties die alleen aan de Comparer‑constructor worden doorgegeven maar niet aan elke Add()‑aanroep – en verifieer vanaf de serverkant in plaats van het uitvoerbestand.

Aanvullende bronnen