Вступ
Колега надсилає два варіанти договору і просить вас порівняти їх. Ви завантажуєте обидва у ваш сервіс порівняння, отримуєте результат, і все виглядає нормально. Ви не помітили, що один із документів містив пов’язане зображення, яке вказувало на URL, і ваш сервер звернувся до цього хоста в момент відкриття файлу. У виводі нічого не підказує, що це сталося.
Це не помилка — це те, що означає «правильне» завантаження документа. Файл OOXML може посилатися на зображення, яке розташоване на веб‑сервері, а не всередині пакету, і і Word, і будь‑яка бібліотека, що правильно завантажує документ, розв’язують це посилання. GroupDocs.Comparison for .NET надає два властивості в LoadOptions, які дозволяють вирішити, чи робити це: SkipExternalResources і WhitelistedResources.
Разом вони дають три конфігурації, і ця стаття порівнює всі три — permissive за замовчуванням, блокування всього та блокування всього, крім іменованих посилань. Після прочитання ви зрозумієте, яку обрати для конкретного джерела документа, а також дві помилки, які змушують ці налаштування виглядати так, ніби вони не працюють.
💡 Повний робочий приклад: block-external-resources-on-document-load-dotnet — консольний проєкт, який самостійно обслуговує посилання на зображення і реєструє кожен запит, щоб ви могли спостерігати, як діє кожне налаштування.
Де ховаються зовнішні посилання
Перш ніж обирати налаштування, варто зрозуміти, що саме ви обираєте. Файл .docx містить зовнішні посилання у двох різних місцях, і їх легко пропустити, бо жодне з них не видно у тексті документа.
Перше — це зв’язок у word/_rels/document.xml.rels, який має TargetMode="External" і абсолютний URL. Зображення з’являється в тілі як малюнок, який посилається на зв’язок за ID, тому сам URL ніколи не показується поруч із вмістом, який він впливає.
Друге — це поле коду INCLUDEPICTURE у тілі документа, яке містить URL всередині інструкції поля. Word розв’язує його під час рендерингу сторінки; бібліотека порівняння розв’язує його під час завантаження документа.
Обидва механізми дотримуються двох параметрів завантаження, описаних нижче, що важливо, бо документ може використовувати будь‑який або обидва. Посилання, яке ви помітили у файлі зв’язків, не є доказом того, що у полі коду немає другого посилання.
Підхід 1: За замовчуванням — посилання розв’язуються
SkipExternalResources за замовчуванням має значення false, тому документ, завантажений без додаткових налаштувань, розв’язує свої віддалені посилання:
LoadOptions loadOptions = new LoadOptions
{
SkipExternalResources = false
};
using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
comparer.Add(targetPath, loadOptions);
comparer.Compare(outputPath);
}
Це забезпечує найвищу достовірність: порівнювані документи містять усе, на що вони посилаються, точно так, як їх відобразив би Word. Для документів, створених вашою власною програмою або шаблонами, де кожен URL посилається на інфраструктуру, яку ви контролюєте, це правильний вибір — і відсутнє пов’язане зображення може зробити порівняння активним оманливим.
Недолік — кожне посилання контактує з тим, хто його розмістив. Є також затримка, не пов’язана з довірою: URL, який більше не розв’язується, змушує процес завантаження чекати повну спробу підключення під час кожного порівняння.
Підхід 2: Блокувати всі зовнішні ресурси
Одна властивість вимикає розв’язання віддалених посилань для даного документа:
LoadOptions loadOptions = new LoadOptions
{
SkipExternalResources = true
};
using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
comparer.Add(targetPath, loadOptions);
comparer.Compare(outputPath);
}
Запит не надсилається. Посилання на зображення відсутні у результаті, і — це важливо уточнити — нічого іншого не змінюється. Налаштування визначає, що саме завантажується, а не як знаходяться відмінності, тому текстові та структурні зміни між двома документами виявляються так само, як і раніше. Єдине, що ви втрачаєте, — можливість виявити зміну всередині посиланого зображення, яке ніколи не було завантажено.
Це конфігурація, яку варто використовувати як базову для документів, які ви не створювали: завантаження користувачами у веб‑додатку, файли, отримані електронною поштою, будь‑яке порівняння на збірочному агенті, де вихідний запит рідко потрібен. Це «все або нічого», хоча — зображення, яке ви дійсно хотіли, буде заблоковано разом із рештою, і результат просто його не міститиме, не повідомляючи про це.
Підхід 3: Блокувати все, крім іменованих посилань
Третя конфігурація — це та, що вимагає уважного читання. WhitelistedResources приймає List<string> і використовується лише коли 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);
}
Записи — це фрагменти URL, а не імена файлів. Кожен з них порівнюється з URL посилання, і збіг у будь‑якому місці URL дозволяє завантажити цей ресурс. Саме це робить білий список переносимим: "includepicture-field.png" дозволяє зображення незалежно від схеми, хоста та шляху, що передують йому, тому один і той самий список працює і в розробці, і в продакшені без переписування.
Той самий параметр може працювати і протилежно. Короткий або загальний фрагмент — logo.png, або ще гірше, .png — може збігатися з посиланнями, які ви ніколи не планували дозволяти. Оберіть фрагмент, достатньо специфічний, щоб ідентифікувати саме той ресурс, який вам потрібен.
У наведеному прикладі посилань ця конфігурація завантажує білим списком дозволене зображення і блокує друге посилання, яке не потрапляє під жоден запис. Журнал запитів показує три запити, тоді як permissive за замовчуванням створював п’ять, і виводить лише файл, що знаходиться у білому списку.
Яку конфігурацію обрати?
Підібрати налаштування треба відповідно до джерела документа. Документи, створені вашою програмою або шаблонами, можуть залишатися за замовчуванням, бо кожен URL посилається на інфраструктуру, яку ви вже контролюєте. Будь‑який документ, що надходить ззовні — завантаження користувачами, вкладення електронної пошти, файли третіх сторін — вимагає SkipExternalResources = true. Додавайте вузький фрагмент у WhitelistedResources лише тоді, коли один довірений ресурс дійсно має бути розв’язаний.
Порівняння трьох варіантів
| Питання | За замовчуванням | Блокувати все | Блокувати + білий список |
|---|---|---|---|
| Кількість властивостей для встановлення | 0 | 1 | 2 |
| Вихідні запити | всі посилання | жодного | лише дозволені |
| Управління окремим посиланням | ні | ні | так |
| Мертві URL впливають на час завантаження | так | ні | лише дозволені |
| Найкраще підходить для | документів, створених вами | документів з будь‑якого іншого джерела | довірених шаблонів серед недовіреного вмісту |
Рішення базується на походженні документа, а не на продуктивності. Документи, створені вашими системами, можуть залишатися за замовчуванням. Документи ззовні — блокувати. Білий список використовуйте лише тоді, коли конкретне довірене посилання має залишитися активним — наприклад, корпоративний шаблон, який бере заголовкове зображення з внутрішнього URL, серед звітів, у яких автори вставляли зображення куди завгодно.
Дві помилки
Обидві помилки дають один і той же симптом: ви встановлюєте параметр, а він, здається, нічого не робить.
Білий список без перемикача. WhitelistedResources використовується лише коли SkipExternalResources встановлено в true. Якщо задати його окремо, він нічого не робить — немає блокування, до якого можна було б зробити виняток. Якщо здається, що білий список ігнорується, спершу перевірте це.
Параметри лише для джерела. Це більш тонка помилка. Параметри завантаження описують, як один документ завантажується. Конструктор Comparer приймає параметри для джерела; кожний виклик Add() приймає параметри для цільового документа:
using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
comparer.Add(targetPath, loadOptions);
comparer.Compare(outputPath);
}
Якщо передати їх лише в конструктор і забути про Add(), джерело буде захищене, а кожна ціль все ще буде завантажувати свої посилання. Порівняння проходить, результат виглядає правдоподібно, і половина ваших документів все ще виходить в мережу. Коли джерело і ціль потребують різного поводження, передайте окремі екземпляри LoadOptions — саме для цього API приймає їх per‑document.
Перевірка, що все працює
Заблокований ресурс залишає майже жодного сліду. У вихідному документі відсутнє зображення, що виглядає так само, ніби його ніколи не було. Тому читання результатного файлу — поганий спосіб підтвердити, що налаштування спрацювало.
Наблюдайте за серверною частиною. Приклад посилань навмисно використовує такий підхід: він запускає невеликий HTTP‑слухач на вільному порті loopback, записує демонстраційні документи, що посилаються на цей порт, і реєструє кожен отриманий запит, виводячи кількість запитів на кожне порівняння. П’ять запитів, потім нуль, потім три. Мережевий трасування ваших реальних джерел документів дає таку ж впевненість.
Висновок
Три конфігурації, одне правило вибору: залишайте за замовчуванням документи, які ви створили, встановлюйте SkipExternalResources = true для всього іншого і додавайте вузький фрагмент URL у білий список лише там, де конкретне довірене посилання має залишитися активним.
Потім перевірте два моменти, які безшумно скасовують вашу роботу — білий список без SkipExternalResources = true і параметри, передані лише конструктору Comparer, а не кожному виклику Add() — і верифікуйте результат зі сторони сервера, а не вихідного файлу.
Додаткові ресурси
- Configuring external resource loading in GroupDocs.Comparison for .NET — посібник з використанням, з матрицею рішень та FAQ
- block-external-resources-on-document-load-dotnet — повний runnable приклад, включаючи хост зображень на loopback
- Load password-protected documents —
LoadOptions.Password, схожий параметр з тим же правилом області застосування per‑document - Load custom fonts — розв’язання нестандартних шрифтів під час завантаження за допомогою
LoadOptions.FontDirectories - GroupDocs.Comparison for .NET API reference — повна документація
LoadOptionsта класуComparer - Free support forum — питання щодо обробки зовнішніх ресурсів та поведінки порівняння