Wprowadzenie
Współpracownik przesyła dwie wersje umowy i prosi Cię o porównanie ich. Umieszczasz oba pliki w usłudze porównywania, wynik wraca i wszystko wygląda normalnie. Nie zauważyłeś, że jeden z dokumentów zawierał powiązany obrazek wskazujący na adres URL, a Twój serwer skontaktował się z tym hostem w momencie otwarcia pliku. Żaden element wyniku nie informuje Cię, że to się stało.
To nie jest błąd – to właśnie oznacza prawidłowe ładowanie dokumentu. Plik OOXML może odwoływać się do obrazu znajdującego się na serwerze internetowym, a zarówno Word, jak i każda biblioteka prawidłowo ładująca dokument, rozwiązuje to odwołanie. GroupDocs.Comparison for .NET udostępnia dwie właściwości w LoadOptions, które pozwalają zdecydować, czy ma to miejsce: SkipExternalResources i WhitelistedResources.
W połączeniu dają trzy konfiguracje, a ten artykuł porównuje wszystkie trzy – domyślną permissywną, blokującą wszystko oraz blokującą wszystko oprócz nazwanych odwołań. Po przeczytaniu będziesz wiedział, którą wybrać dla konkretnego źródła dokumentu oraz dwa typowe błędy, które sprawiają wrażenie, że ustawienia nie działają.
💡 Pełny działający przykład: block-external-resources-on-document-load-dotnet – projekt konsolowy, który sam serwuje odwoływane obrazy i loguje każde żądanie, dzięki czemu możesz obserwować, jak każde ustawienie wchodzi w życie.
Gdzie ukrywają się zewnętrzne odwołania
Zanim wybierzesz ustawienie, warto wiedzieć, co dokładnie wybierasz. Plik .docx przechowuje zewnętrzne odwołania w dwóch odrębnych miejscach i łatwo je przeoczyć, ponieważ żadne nie jest widoczne w treści dokumentu.
Pierwsze to relacja w word/_rels/document.xml.rels zawierająca TargetMode="External" oraz pełny adres URL. Obraz pojawia się w treści jako rysunek, który odwołuje się do tej relacji po ID, więc sam URL nigdy nie pojawia się w pobliżu treści, którą wpływa.
Drugie to kod pola INCLUDEPICTURE w ciele dokumentu, w którym URL jest umieszczony wewnątrz instrukcji pola. Word rozwiązuje je podczas renderowania strony; biblioteka porównująca rozwiązuje je przy ładowaniu dokumentu.
Oba mechanizmy respektują dwie omawiane poniżej opcje ładowania, co ma znaczenie, ponieważ dokument może używać jednego, drugiego lub obu. Odwołanie, które zauważyłeś w pliku relacji, nie jest dowodem, że nie istnieje drugie w kodzie pola.
Podejście 1: Domyślne – odwołania rozwiązywane
SkipExternalResources domyślnie ma wartość false, więc dokument załadowany bez dodatkowej konfiguracji rozwiązuje swoje zdalne odwołania:
LoadOptions loadOptions = new LoadOptions
{
SkipExternalResources = false
};
using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
comparer.Add(targetPath, loadOptions);
comparer.Compare(outputPath);
}
Daje to najwyższą wierność: porównywane dokumenty zawierają wszystko, do czego odwołują się, dokładnie tak, jak Word by je wyświetlił. Dla dokumentów tworzonych przez Twoją aplikację lub szablony, gdzie każdy URL odwołania wskazuje na infrastrukturę, którą zarządzasz, jest to właściwy wybór – a brak powiązanego obrazu może sprawić, że porównanie będzie wprowadzające w błąd.
Kosztem jest to, że każde odwołanie jest kontaktowane, niezależnie od tego, kto je umieścił. Dodatkowo istnieje koszt czasowy, niezwiązany z zaufaniem: URL, który już nie istnieje, powoduje, że ładowanie czeka na pełną próbę połączenia przy każdym porównaniu.
Podejście 2: Blokowanie wszystkich zewnętrznych zasobów
Jedna właściwość wyłącza rozwiązywanie zdalnych odwołań dla danego dokumentu:
LoadOptions loadOptions = new LoadOptions
{
SkipExternalResources = true
};
using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
comparer.Add(targetPath, loadOptions);
comparer.Compare(outputPath);
}
Żadne żądanie nie jest wysyłane. Odwoływane obrazy nie pojawiają się w wyniku i – to jest ważne – nic innego się nie zmienia. Ustawienie określa, co jest ładowane, a nie jak wykrywane są różnice, więc zmiany tekstowe i strukturalne między dwoma dokumentami są wykrywane dokładnie tak jak wcześniej. Jedyną rzeczą, którą tracisz, jest możliwość wykrycia zmiany wewnątrz odwołanego obrazu, który nigdy nie został załadowany.
To konfiguracja, którą warto traktować jako bazową dla dokumentów, których nie stworzyłeś: przesyłane przez użytkowników w aplikacji webowej, pliki otrzymane e‑mailem, wszystko porównywane na agencie budującym, gdzie wychodzące żądania są rzadko pożądane. Jest to podejście „wszystko albo nic”, choć – obrazek, który naprawdę chciałeś, zostaje zablokowany razem z resztą, a wynik po prostu go nie zawiera, nie informując o tym.
Podejście 3: Blokowanie wszystkiego oprócz nazwanych odwołań
Trzecia konfiguracja nagradza dokładną analizę. WhitelistedResources przyjmuje List<string> i jest sprawdzana tylko wtedy, gdy SkipExternalResources ma wartość 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);
}
Elementy listy są fragmentami URL, a nie nazwami plików. Każdy z nich jest dopasowywany do odwołania URL, a dopasowanie w dowolnym miejscu URL zezwala na załadowanie tego zasobu. To właśnie sprawia, że biała lista jest przenośna: "includepicture-field.png" zezwala na obrazek niezależnie od schematu, hosta i ścieżki poprzedzającej go, więc ta sama lista działa w środowisku deweloperskim i produkcyjnym bez konieczności modyfikacji.
Ta sama właściwość może działać w drugą stronę. Krótki lub ogólny fragment – logo.png, a jeszcze gorzej .png – może dopasować odwołania, które nigdy nie miały być dozwolone. Wybierz fragment wystarczająco specyficzny, aby jednoznacznie identyfikował jedyny zasób, który chcesz zezwolić.
W przykładowym odwołaniu ta konfiguracja pobiera białą listę obrazu i pozostawia drugi odwołany obraz, którego żaden wpis nie obejmuje, zablokowany. Log żądań pokazuje trzy żądania, podczas gdy domyślna permissywna konfiguracja wygenerowała pięć, i wymienia jedynie plik z białej listy.
Którą konfigurację wybrać?
Dopasuj ustawienie do pochodzenia dokumentu. Dokumenty generowane przez Twoją aplikację lub szablony mogą pozostać przy domyślnym ustawieniu, ponieważ każdy URL odwołania wskazuje na infrastrukturę, którą już kontrolujesz. Wszystko, co przychodzi z zewnątrz – przesyłane przez użytkowników, załączniki e‑mailowe, pliki stron trzecich – wymaga SkipExternalResources = true. Dodaj wąski fragment WhitelistedResources tylko wtedy, gdy jedno zaufane odwołanie naprawdę musi zostać rozwiązane.
Porównanie trzech konfiguracji
| Kwestia | Domyślne | Blokuj wszystkie | Blokuj + biała lista |
|---|---|---|---|
| Liczba właściwości do ustawienia | 0 | 1 | 2 |
| Żądania wychodzące | wszystkie odwołania | żadne | tylko z białej listy |
| Kontrola per‑odwołanie | nie | nie | tak |
| Martwe URL‑e zwiększają czas ładowania | tak | nie | tylko z białej listy |
| Najlepsze dla | dokumentów wygenerowanych przez Ciebie | dokumentów pochodzących z zewnątrz | zaufanych szablonów wśród niepewnej treści |
Decyzja zależy od pochodzenia dokumentu, a nie od wydajności. Dokumenty tworzone przez Twoje systemy mogą pozostać przy domyślnym ustawieniu. Dokumenty z zewnątrz wymagają blokady. Biała lista ma sens tylko wtedy, gdy konkretny odwołany zasób musi zostać rozwiązany – np. szablon firmowy pobierający nagłówek z wewnętrznego URL, wśród raportów, w których autorzy wklejają obrazy z dowolnych źródeł.
Dwa typowe błędy
Oba te błędy dają ten sam objaw: ustawiasz opcję, a wydaje się, że nic się nie dzieje.
Biała lista bez włącznika. WhitelistedResources jest sprawdzana wyłącznie wtedy, gdy SkipExternalResources ma wartość true. Ustawiona samodzielnie nie robi nic – nie ma blokady, której mogłaby zrobić wyjątek. Jeśli biała lista wydaje się ignorowana, sprawdź to najpierw.
Opcje tylko dla źródła. To bardziej subtelny błąd. Opcje ładowania opisują, jak jeden dokument jest ładowany. Konstruktor Comparer przyjmuje opcje dla źródła; każde wywołanie Add() przyjmuje opcje dla danego celu:
using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
comparer.Add(targetPath, loadOptions);
comparer.Compare(outputPath);
}
Przekazujesz je do konstruktora i zapominasz o wywołaniu Add(), wówczas źródło jest chronione, a każdy cel wciąż pobiera swoje odwołania. Porównanie kończy się sukcesem, wynik wygląda wiarygodnie, a połowa dokumentów nadal łączy się z siecią. Gdy źródło i cel wymagają innego traktowania, przekaż oddzielne instancje LoadOptions – właśnie po to API przyjmuje je per dokument.
Jak zweryfikować, że rzeczywiście zadziałało
Zablokowany zasób pozostawia prawie żadnych śladów. Dokument wyjściowy po prostu nie zawiera obrazu, co wygląda jak dokument, który nigdy nie miał obrazu. Czytanie pliku wynikowego to więc słaby sposób na potwierdzenie, że ustawienie zadziałało.
Obserwuj stronę serwera. Przykład odwołania przyjmuje tę metodę celowo: uruchamia mały nasłuch HTTP na wolnym porcie loopback, zapisuje dokumenty demonstracyjne wskazujące na ten port i loguje każde otrzymane żądanie, wypisując liczbę żądań na porównanie. Pięć żądań, potem zero, potem trzy. Śledzenie sieciowe wobec rzeczywistych źródeł dokumentów daje tę samą pewność.
Podsumowanie
Trzy konfiguracje, jedna reguła decyzyjna: pozwól dokumentom, które sam wygenerowałeś, pozostać przy domyślnym ustawieniu, ustaw SkipExternalResources = true dla wszystkiego innego i dodaj wąski fragment URL do białej listy tylko tam, gdzie konkretny zaufany odwołany zasób musi zostać rozwiązany.
Następnie sprawdź dwa elementy, które cicho cofają Twoją pracę – białą listę bez SkipExternalResources = true oraz opcje przekazane do konstruktora Comparer, ale nie do każdego wywołania Add() – i weryfikuj z poziomu serwera, a nie pliku wyjściowego.
Dodatkowe zasoby
- Configuring external resource loading in GroupDocs.Comparison for .NET – przewodnik po scenariuszach użycia, z macierzą decyzyjną i FAQ
- block-external-resources-on-document-load-dotnet – kompletny, uruchamialny przykład, w tym host obrazów w pętli loopback
- Load password-protected documents –
LoadOptions.Password, pokrewna opcja o tym samym zakresie per‑dokument - Load custom fonts – rozwiązywanie niestandardowych czcionek podczas ładowania przy użyciu
LoadOptions.FontDirectories - GroupDocs.Comparison for .NET API reference – pełna dokumentacja
LoadOptionsi klasyComparer - Free support forum – pytania o obsługę zasobów zewnętrznych i zachowanie porównywania