Чистая Windows и разработка «без всего»
Есть компьютер с чистой копией Windows, без доступа в интернет и без каких‑либо установленных средств разработки. Только одна чистая пользовательская «венда». Не поверите, но даже в таких спартанских условиях возможно написать и запустить полноценную программу. И сейчас я расскажу как.
Ради этого скриншота я честно развернул пользовательскую версию Windows 11 в виртуальной машине. Чего не сделаешь ради искусства!
Ужасы познания
На самом деле в ОС семейства Windows с самого их начала было внутри столько всякого интересного, что никакой статьи не хватит описать, но почему-то мало кто об этом знает даже из разработчиков, особенно современных.
Спросите ради интереса знакомого программиста, возможно ли программировать на «чистой» пользовательской Windows без установки Visual Studio — удивитесь ответам.
Ну и разумеется насаждаемый «пользовательский» подход самой Microsoft, которая ковыряние во внутренностях своих продуктов мягко говоря никогда не одобряла, создал ореол простоты и надежности, без необходимости разбираться как оно внутри устроено.
Поэтому описанное ниже наверное вызовет определенный ужас как у обычных пользователей так и некоторых программистов — особенно если они обучались по видеокурсам ничего не знают об истории ОС Windows.
Начну с цитаты из одной интересной статьи:
Over the past few months, I've received several variations on this question for other operating systems and all of the released versions of the .NET Framework. When the .NET Framework is installed as a part of the OS, it does not appear in the Programs and Features (or Add/Remove Programs) control panel. The following is a complete list of which version of the .NET Framework is included in which version of the OS
И ниже длинный такой список с версиями. А вот еще один если вдруг первого оказалось недостаточно.
Ну казалось бы и.. что? Чего тут такого?
Про .NET SDK все и так знают, временами его необходимо установить «для запуска игор», временами он сам ставится в виде зависимой библиотеки и никому не мешает.
Все так, да.
Только что-то мне подсказывает внутрь вы не заглядывали, правда? Поэтому на что эта штука на самом деле способна не представляете.
А я представляю и сейчас расскажу.
Заходите в папку Windows на вашем компьютере, вот сюда:
Файлт csc.exe — самый настоящий компилятор, фактически портал в ад на вашем обычном домашнем компьютере.
Почему все так страшно?
Потому что через какое-то время вы обнаружите себя сильно заросшим, с бородой и красными глазами, проводящим ночи за компьютером и медленно мутирующим в программиста.
Шучу.
А если серьезно:
появляется возможность создания нативных программ сразу на вашем компьютере, минуя стадию проверки электронной подписи, проверки антивирусом, проверки электронного письма и так далее.
В отличие от VB или PowerShell-скриптов, которые анализируются перед запуском любым приличным антивирусом, антивирусы не анализируют исходный код программ на C# и куда лояльнее относятся к программам собранным локально на этой же машине.
Так что веселье начинается.
Простой пример
Для начала будет простой пример, который просто показывает стандартный диалог с сообщением. Именно его в запущенном виде вы можете видеть на заглавной картинке в статье.
Весь процесс от кода до запуска я записал на видео:
Исходный код тут казалось бы максимально простой, но с одним интересным нюансом про который ниже:
using System;
using System.Runtime.InteropServices;
namespace yoba
{
class Program
{
// импортирование нативной WinAPI функции MessageBox.
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
static void Main(string[] args)
{
//вызываем и показываем диалог
MessageBox(IntPtr.Zero, "Йоу!", "Добро пожаловать в разработку!", 0);
}
}
}
Сохраняете этот текст обычным «блокнотом» в файл yoba.cs и запускаете сборку:
c:\Windows\Microsoft.NET\Framework\v3.5\csc.exe yoba.cs
Таким образом я запускал сборку на Windows 10, но имейте ввиду что версия системного .NET SDK может отличаться и например в Windows 11 уже будет:
c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe yoba.cs
После сборки рядом с исходным файлом yoba.cs появится бинарник yoba.exe, который вы сможете запустить.
А теперь про нюанс.
Нюанс
Существует определенное предубеждение по отношению к managed‑языкам вроде Java и С# — они не подходят для серьезных дел вроде написания эксплоитов, использования 0day‑уязвимостей и пенетрации ядра.
Что все подобные вещи творят
в глубокой тайнена чистом Си, в крайнем случае на C++ а все эти ваши Java/C# не более чем «погремушки для детей», не достойные даже косого взгляда серьезного профессионала.
Вот тут и начинается нюанс, посмотрите на эту радость:
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd,
string lpText, string lpCaption, uint uType);
Это мои дорогие читатели, ни что иное как вызов нативного WinAPI, с помощью которого творили всякое нехорошее в далекие 90е.
C# и .NET имеет оооочень глубокую интеграцию с Windows, несмотря на всю свою «безопасность» и управляемость, поэтому легко и просто может заменить собой и Си и С++ в качестве инструмента для нехороших дел.
И оно живет на вашем компьютере, дома и в офисе, с постоянной пропиской и регистрацией.
Но разумеется столь простого примера несколько мало для осознания глубины проблемы, поэтому я подготовил кое-что более серьезное.
Сложный пример: выключаем Windows
Итак, это будет относительно небольшое приложение на C#, выключающее компьютер без предупреждения и подтверждения пользователя. И само собой без прав администратора.
Просто так, внезапно.
Последствия думаю каждый из читателей сможет оценить для себя сам.
Весь процесс на видео (разумеется это виртуальная машина):
А теперь код:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Diagnostics;
using System.Management;
using System.Security.Permissions;
using System.Runtime.InteropServices;
namespace yoba
{
// See http://www.developmentnow.com/g/33_2004_12_0_0_33290/Access-...
// Calling this code on backup/restore seems to enable BCD
public class TokenHelper
{
// PInvoke stuff required to set/enable security privileges
[DllImport("advapi32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int OpenProcessToken(
System.IntPtr ProcessHandle, // handle to process
int DesiredAccess, // desired access to process
ref IntPtr TokenHandle // handle to open access token
);
[DllImport("kernel32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int AdjustTokenPrivileges(
IntPtr TokenHandle,
int DisableAllPrivileges,
IntPtr NewState,
int BufferLength,
IntPtr PreviousState,
ref int ReturnLength);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
ref LUID lpLuid);
[StructLayout(LayoutKind.Sequential)]
internal struct LUID
{
internal int LowPart;
internal int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
struct LUID_AND_ATTRIBUTES
{
LUID Luid;
int Attributes;
}
[StructLayout(LayoutKind.Sequential)]
struct _PRIVILEGE_SET
{
int PrivilegeCount;
int Control;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=1)] // ANYSIZE_ARRAY = 1
LUID_AND_ATTRIBUTES [] Privileges;
}
[StructLayout(LayoutKind.Sequential)]
internal struct TOKEN_PRIVILEGES
{
internal int PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
internal int[] Privileges;
}
const int SE_PRIVILEGE_ENABLED = 0x00000002;
const int TOKEN_ADJUST_PRIVILEGES = 0X00000020;
const int TOKEN_QUERY = 0X00000008;
const int TOKEN_ALL_ACCESS = 0X001f01ff;
const int PROCESS_QUERY_INFORMATION = 0X00000400;
public static bool SetPrivilege (string lpszPrivilege, bool
bEnablePrivilege )
{
bool retval = false;
int ltkpOld = 0;
IntPtr hToken = IntPtr.Zero;
TOKEN_PRIVILEGES tkp = new TOKEN_PRIVILEGES();
tkp.Privileges = new int[3];
TOKEN_PRIVILEGES tkpOld = new TOKEN_PRIVILEGES();
tkpOld.Privileges = new int[3];
LUID tLUID = new LUID();
tkp.PrivilegeCount = 1;
if (bEnablePrivilege)
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
tkp.Privileges[2] = 0;
if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
{
Process proc = Process.GetCurrentProcess();
if(proc.Handle != IntPtr.Zero)
{
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
ref hToken) != 0)
{
tkp.PrivilegeCount = 1;
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
tkp.Privileges[1] = tLUID.HighPart;
tkp.Privileges[0] = tLUID.LowPart;
const int bufLength = 256;
IntPtr tu = Marshal.AllocHGlobal( bufLength );
Marshal.StructureToPtr(tkp, tu, true);
if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
ltkpOld) != 0)
{
// successful AdjustTokenPrivileges doesn't mean privilege could be changed
if (Marshal.GetLastWin32Error() == 0)
{
retval = true; // Token changed
}
}
TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
typeof(TOKEN_PRIVILEGES) );
Marshal.FreeHGlobal( tu );
}
}
}
if (hToken != IntPtr.Zero)
{
CloseHandle(hToken);
}
return retval;
}
}
class ShutDown
{
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool ExitWindowsEx(int flg, int rea);
internal const int EWX_FORCE = 0x00000004;
internal const int EWX_POWEROFF = 0x00000008;
static void Main(string[] args)
{
TokenHelper.SetPrivilege("SeShutdownPrivilege",true);
ExitWindowsEx(EWX_FORCE | EWX_POWEROFF, 0);
}
}
}
Обращаю внимание что это не эксплоит, не дыра, не баг и не уявимость а вполне себе стандартный функционал. Просто так получилось что о нем мало кто знает.
Собирается по аналогии с предыдущим примером:
c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Shutdown.cs
После запуска компьютер практически немедленно выключится:
проверено и в виртуальной машине и на железе, на 10й и 11й Windows.
Рассказываю как это работает.
Ключевая функция — ExitWindowsEx, которая и отвечает за завершение работы ОС. Эта функция очень старая и известная, существует еще со времен Windows 95.
Но для ее вызова нужны «привилегии», которые и выставляет программно класс TokenHelper.
Константы ниже:
internal const int EWX_FORCE = 0x00000004;
internal const int EWX_POWEROFF = 0x00000008;
используются вместе с "побитовым или" для указания на требуемое действие.
Вот еще допустимые варианты:
internal const int EWX_LOGOFF = 0x00000000;
internal const int EWX_SHUTDOWN = 0x00000001;
internal const int EWX_REBOOT = 0x00000002;
internal const int EWX_FORCEIFHUNG = 0x00000010;
Описание их всех находится все там же — в официальном руководстве, не поверите.
Теперь давайте разбираться как же работает столь жесткое забивание на систему защиты еще и стандартными средствами:
TokenHelper.SetPrivilege("SeShutdownPrivilege",true);
И начнем мы с импортов.
Первое что импортируется это функция OpenProcessToken:
[DllImport("advapi32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int OpenProcessToken(
System.IntPtr ProcessHandle, // handle to process
int DesiredAccess, // desired access to process
ref IntPtr TokenHandle // handle to open access token
);
Функция отвечает за получение данных о наборе «привилегий», связанных с конкретным процессом. Собственно набор таких привилегий и называется «токеном».
Вот как эта функция вызывается:
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
ref hToken) != 0)
{
..
Тут надо отметить передачу по ссылке в стиле Си (ref hToken), когда в функцию передается ссылка на объект C#, дальше функция этот объект заполняет данными. А возвращает она просто true или false — статус выполнения, отработала функция или нет.
Дальше импортируется простая и банальная функция освобождения ресурсов:
[DllImport("kernel32", SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool CloseHandle(IntPtr handle);
Вызывается она в самом конце, после всей логики и нужна только для освобождения использованной памяти под токен привилегий:
if (hToken != IntPtr.Zero)
{
CloseHandle(hToken);
}
Наконец главная функция, непосредственно отвечающая за переключение привилегий:
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern int AdjustTokenPrivileges(
IntPtr TokenHandle,
int DisableAllPrivileges,
IntPtr NewState,
int BufferLength,
IntPtr PreviousState,
ref int ReturnLength);
Вот весь ключевой блок логики смены привилегий:
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
ref hToken) != 0)
{
tkp.PrivilegeCount = 1;
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
tkp.Privileges[1] = tLUID.HighPart;
tkp.Privileges[0] = tLUID.LowPart;
const int bufLength = 256;
IntPtr tu = Marshal.AllocHGlobal( bufLength );
Marshal.StructureToPtr(tkp, tu, true);
if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
ltkpOld) != 0)
{
// successful AdjustTokenPrivileges doesn't mean privilege could be changed
if (Marshal.GetLastWin32Error() == 0)
{
retval = true; // Token changed
}
}
TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
typeof(TOKEN_PRIVILEGES) );
Marshal.FreeHGlobal( tu );
}
Как видите вызов достаточно сложный, используется Сишный процедурный подход к заполнению полей структуры и передачи его по ссылке в вызываемую функцию.
После вызова проверяется наличие ошибки, также в стиле Си:
if (Marshal.GetLastWin32Error() == 0)
{
retval = true; // Token changed
}
0 это код возрата для успешного вызова, если он есть — считается что операция смены привилегий была выполнена успешно.
Наконец последняя функция, про которую стоит рассказать:
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
SuppressUnmanagedCodeSecurityAttribute]
static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
ref LUID lpLuid);
Она отвечает за поиск привилегии по имени, полагаю ведь заметили что мы передаем некое кодовое наименование при вызове TokenHelper:
TokenHelper.SetPrivilege("SeShutdownPrivilege",true);
Именно эта функция отвечает за поиск конкретной привилегии по названию «SeShutdownPrivilege», вот так выглядит ее вызов:
if (bEnablePrivilege)
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
tkp.Privileges[2] = 0;
if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
{
..
Переменная bEnablePrivilege булевая, это и есть то самое true передаваемое в качестве второго аргумента, а блок:
if (bEnablePrivilege)
tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
tkp.Privileges[2] = 0;
Отвечает за формирование правильного вызова с использованием системных констант (SE_PRIVILEGE_ENABLED).
При вызове также передается ссылка (ref tLUID) на объект LUID, который будет содержать после вызова указание на найденную привилегию.
Вот такие интересные дела.
Итого
Все описанное не призыв к немедленным действиям, а лишь повод к размышлению о смысле бытия. Ну там насчет надежности, безопасности и всего такого — что вам продает большая иностранная корпорация.
Задумайтесь, если увидите любимую "венду" на атомной станции или военном объекте — без всяких ЦРУ и хакеров в ОС Windows адова гора функционала, который легко и просто можно использовать во вред.
P.S.
Статья была опубликована на Хабре, более вольный оригинал доступен в нашем блоге.
Использование C# для преобразования HTML в Word или Word в HTML
Преобразование между HTML, стандартным форматом веб-контента, и Word, универсальным форматом офисных документов, является частой задачей во многих корпоративных приложениях. Будь то генерация отчётов Word из динамического содержимого базы данных или публикация существующих документов Word в виде веб-страниц — владение эффективными методами конвертации значительно повышает производительность.
В этой статье мы расскажем, как с помощью профессионального компонента для работы с документами Spire.Doc for .NET и кода на C# легко выполнять взаимное преобразование между HTML и Word.
Почему стоит выбрать Spire.Doc for .NET?
Сам по себе .NET не предоставляет встроенной поддержки для работы с документами Word или HTML и их преобразования. Хотя существуют открытые решения (например, HtmlAgilityPack в сочетании с OpenXML SDK), они часто требуют от разработчика ручной обработки множества деталей и имеют недостатки в сохранении стилей, встраивании изображений и т.д.
Spire.Doc for .NET предлагает мощный и простой в использовании API, позволяющий создавать, редактировать и конвертировать документы без установки Microsoft Office на сервере. Он обеспечивает высокое качество преобразования:
Сохранение стилей: полная поддержка текстовых форматов, цветов, выравнивания и других CSS-стилей из HTML.
Обработка изображений: автоматическое распознавание и встраивание изображений из тегов <img> в HTML.
Табличная структура: сохранение исходной вёрстки HTML-таблиц без искажений.
Простота разработки: лаконичный API и низкий порог вхождения.
Подготовка: установка Spire.Doc
Перед началом кодирования необходимо добавить ссылку на Spire.Doc в проект. Рекомендуется выполнить установку через консоль диспетчера пакетов NuGet с помощью следующей команды:
Install-Package Spire.Doc
Сценарий 1: преобразование HTML-строки в Word
Этот сценарий очень гибок и подходит для случаев, когда HTML-содержимое получается из базы данных, API-интерфейсов или других динамических источников данных, и требуется мгновенно сгенерировать документ Word.
В приведённом ниже коде показано, как прочитать содержимое HTML-файла (в виде строки) и преобразовать его в файл .docx:
using Spire.Doc;
using Spire.Doc.Documents;
using System.IO;
namespace ConvertHtmlStringToWord
{
class Program
{
static void Main(string[] args)
{
// 1. Создание объекта Document
Document document = new Document();
// 2. Добавление раздела (Section)
Section section = document.AddSection();
// Опционально: установка полей страницы
section.PageSetup.Margins.All = 2;
// 3. Добавление абзаца (Paragraph)
Paragraph paragraph = section.AddParagraph();
// 4. Чтение HTML-строки из файла
string htmlFilePath = @"C:\Users\Administrator\Desktop\Html.html";
string htmlString = File.ReadAllText(htmlFilePath, System.Text.Encoding.UTF8);
// 5. Вставка HTML-строки в абзац
paragraph.AppendHTML(htmlString);
// 6. Сохранение в документ Word
document.SaveToFile("AddHtmlStringToWord.docx", FileFormat.Docx);
// 7. Освобождение ресурсов
document.Dispose();
}
}
}
Описание ключевых методов :
Paragraph.AppendHTML(htmlString) — это основной метод преобразования. Он анализирует переданную HTML-строку и полностью преобразует её форматирование, изображения и структуру в содержимое абзаца Word.
С помощью document.SaveToFile() можно указать формат вывода FileFormat.Docx.
Сценарий 2: прямое преобразование HTML-файла в Word
Если у вас уже есть готовый HTML-файл, который вы хотите напрямую преобразовать в документ Word, код будет ещё более лаконичным:
using Spire.Doc;
namespace ConvertHtmlToWord
{
class Program
{
static void Main(string[] args)
{
// 1. Создание объекта Document
Document document = new Document();
// 2. Непосредственная загрузка HTML-файла
document.LoadFromFile(@"C:\Users\Administrator\Desktop\MyHtml.html", FileFormat.Html);
// 3. Сохранение в документ Word
document.SaveToFile("HtmlToWord.docx", FileFormat.Docx);
// 4. Освобождение ресурсов
document.Dispose();
}
}
}
Этот способ позволяет выполнить преобразование за один шаг — метод Document.LoadFromFile() напрямую поддерживает загрузку файлов в формате FileFormat.Html, избавляя от необходимости вручную считывать содержимое файла.
Важные замечания
На практике следует обратить внимание на несколько аспектов, чтобы добиться наилучшего результата преобразования:
Поддержка CSS-стилей : Spire.Doc поддерживает большинство распространённых CSS-свойств, таких как font-size, color, text-align и др. Однако для очень сложных CSS-макетов возможны незначительные расхождения в итоговом отображении.
Пути к изображениям : теги <img> в HTML автоматически преобразуются в изображения в Word. Убедитесь, что пути к изображениям (как локальные, так и URL-адреса) доступны во время конвертации.
Табличная вёрстка : для сложных таблиц рекомендуется избегать специальных свойств вроде table-layout: fixed, чтобы обеспечить корректное отображение таблиц в Word.
Производительность : для больших файлов с большим объёмом содержимого или изображениями высокого разрешения рекомендуется выполнять преобразование в фоновом потоке или асинхронной задаче, чтобы не блокировать основной поток.
Заключение
С помощью Spire.Doc for .NET разработчики могут реализовать высококачественное и высокоточное взаимное преобразование HTML и Word в проектах на C#, используя минимальный объём кода. Будь то обработка динамически генерируемых HTML-строк или пакетное преобразование существующих HTML-файлов — эта библиотека предлагает стабильное и эффективное решение. Она помогает быстро выстроить автоматизированный процесс работы с документами, позволяя сосредоточиться на бизнес-логике приложения.
Работа с водяными знаками в Word на C#: добавление, водяной знак-изображение и удаление — всё в одной статье
В процессе разработки программных систем функции обработки документов часто являются важной частью корпоративных приложений. Документы Word, как один из самых популярных офисных форматов, нередко требуют защиты содержимого и маркировки статуса. Водяные знаки, как визуальные элементы, не мешающие чтению основного текста, но при этом чётко передающие свойства документа (например, уровень конфиденциальности, принадлежность авторских прав или статус черновика), играют важную роль в управлении документами.
В этой статье подробно рассматривается, как с помощью компонента Free Spire.Doc for .NET реализовать на C# добавление текстовых водяных знаков, добавление водяных знаков-изображений и удаление существующих водяных знаков из документов Word.
1. Обзор компонента Spire.Doc for .NET
Free Spire.Doc for .NET — это профессиональный компонент для работы с документами Word, который позволяет разработчикам создавать, читать, изменять и конвертировать документы Word напрямую через код без установки Microsoft Office. Компонент предоставляет полный набор возможностей для работы с водяными знаками и поддерживает два основных типа:
В отличие от сложных решений, имитирующих водяные знаки через колонтитулы или вставку фигур, Spire.Doc содержит встроенную объектную модель для работы с водяными знаками, что делает их добавление и управление более простым и стандартизированным.
Настройка окружения
Перед началом кодирования необходимо установить Free Spire.Doc в проект через диспетчер пакетов NuGet. Выполните следующую команду в «Консоли диспетчера пакетов» Visual Studio:
Install-Package FreeSpire.Doc
После установки необходимо подключить следующие пространства имён в файле кода:
using Spire.Doc;
using Spire.Doc.Documents;
using System.Drawing; // Для работы с цветами и изображениями
2. Добавление текстового водяного знака
Текстовый водяной знак — это самая базовая и наиболее часто используемая форма водяных знаков. По сути, это строка текста, отображаемая на фоне каждой страницы документа в полупрозрачном или заданном цвете. Обычно используется крупный шрифт с диагональным или горизонтальным расположением, чтобы визуально отличаться от основного содержимого.
Описание основных свойств
Класс TextWatermark предоставляет следующие настраиваемые свойства:
Реализация кода
using System;
using System.Drawing;
using Spire.Doc;
using Spire.Doc.Documents;
namespace InsertTextWatermark
{
internal class Program
{
static void Main(string[] args)
{
// Создание экземпляра объекта Document
Document document = new Document();
// Загрузка документа Word с диска
document.LoadFromFile("input.docx");
// Вставка текстового водяного знака
InsertTextWatermark(document);
// Сохранение документа
document.SaveToFile("TextWatermark.docx", FileFormat.Docx);
}
private static void InsertTextWatermark(Document document)
{
TextWatermark txtWatermark = new TextWatermark();
txtWatermark.Text = "НЕ КОПИРОВАТЬ"; // Текст водяного знака
txtWatermark.FontSize = 50; // Размер шрифта
txtWatermark.Color = Color.Blue; // Цвет текста
txtWatermark.Layout = WatermarkLayout.Diagonal; // Диагональное расположение
document.Watermark = txtWatermark;
}
}
}
Ключевые моменты
Водяной знак связан со всем объектом документа, а не с отдельной страницей или разделом. Это означает, что после добавления водяной знак автоматически применяется ко всем страницам документа.
При сохранении необходимо явно указать формат FileFormat.Docx, чтобы информация о водяном знаке была корректно записана в структуру файла Word.
Если требуется горизонтальное центрированное отображение водяного знака, установите свойство Layout в WatermarkLayout.Horizontal.
3. Добавление водяного знака-изображения
По сравнению с текстовыми водяными знаками, водяные знаки-изображения подходят для более широкого круга сценариев:
Добавление логотипа компании в качестве водяного знака во внутренние документы
Наложение изображений с авторскими правами на дизайнерские макеты
Использование изображения подписи как утверждающего водяного знака документа
Описание основных свойств
Класс PictureWatermark предоставляет следующие настраиваемые свойства:
Реализация кода
using System;
using System.Drawing;
using Spire.Doc;
using Spire.Doc.Documents;
namespace InsertImageWatermark
{
internal class Program
{
static void Main(string[] args)
{
// Создание экземпляра объекта Document
Document document = new Document();
// Загрузка документа Word
document.LoadFromFile("Input.docx");
// Вставка водяного знака-изображения
InsertImageWatermark(document);
// Сохранение документа
document.SaveToFile("InsertImageWatermark.docx", FileFormat.Docx);
}
private static void InsertImageWatermark(Document document)
{
PictureWatermark picture = new PictureWatermark();
picture.Picture = Image.FromFile("watermark.png"); // Загрузка изображения
picture.Scaling = 200; // Масштаб (%)
picture.IsWashout = false; // Использовать ли эффект выцветания
document.Watermark = picture;
}
}
}
Два визуальных эффекта для водяного знака-изображения
4. Удаление водяного знака
Жизненный цикл документа обычно включает несколько этапов, и необходимость в водяных знаках не всегда постоянна. Например, документ на стадии проверки может требовать заметного водяного знака «Конфиденциально», но после завершения проверки и официальной публикации водяной знак может стать лишним и потребовать удаления.
Принцип работы
В Spire.Doc удаление водяного знака реализовано очень просто: свойство Document.Watermark по сути является ссылкой на объект. Когда оно указывает на экземпляр TextWatermark или PictureWatermark, это означает, что водяной знак активен. Если же присвоить этому свойству пустую ссылку, водяной знак будет удалён из документа.
Реализация кода
using Spire.Doc;
namespace RemoveWatermark
{
class Program
{
static void Main(string[] args)
{
// Создание экземпляра Document
Document doc = new Document();
// Загрузка документа Word с водяным знаком
doc.LoadFromFile("Input.docx");
// Удаление водяного знака
doc.Watermark = null;
// Сохранение результата
doc.SaveToFile("RemoveWatermark.docx", FileFormat.Docx2013);
}
}
}
⚠️ Важные замечания
Этот способ удаления работает только для стандартных водяных знаков, добавленных через Spire.Doc. Если «водяной знак» реализован нестандартным способом (например, полупрозрачная фигура в колонтитуле или встроенное фоновое изображение), данный метод не сработает — потребуется использовать другие стратегии удаления содержимого документа.
5. Заключение
С помощью Spire.Doc for .NET разработчики могут легко и стандартизированно добавлять и удалять водяные знаки в документах Word с использованием C#. Основные операции, рассмотренные в статье:
Рекомендации по применению
Текстовые водяные знаки : подходят для сценариев с динамической генерацией текста, например отображения различных уровней конфиденциальности в зависимости от прав пользователя
Водяные знаки-изображения : подходят для брендированных сценариев, когда компания использует единый логотип в качестве фона документа
Удаление водяных знаков : подходит для документооборота, например когда документы содержат водяной знак на этапе внутреннего согласования и очищаются перед внешней публикацией
Это решение не зависит от среды Microsoft Office и подходит для развёртывания в серверных приложениях и фоновых службах, обеспечивая хорошую стабильность и масштабируемость.
Почему паттерны GoF наконец начали укладываться у меня в голове только с практикой
С паттернами GoF у меня долго была одна и та же проблема: читаешь описание, вроде все логично, примеры тоже понятны, а потом открываешь реальный код — и все это знание куда-то испаряется.
Особенно тяжело было с паттернами, которые на бумаге выглядят почти очевидно, а вживую постоянно путаются между собой.
Я пришел в .NET из 1С, и у меня с этим был затык. Книги и статьи давали теорию, но не давали ощущения, что ты действительно начал узнавать эти вещи в коде.
В какой-то момент я поменял подход. Вместо того чтобы просто читать, стал учиться через короткие практические задачи:
- посмотреть на код и понять, какой здесь паттерн;
- попробовать объяснить, почему это именно он;
- сравнить с похожими вариантами, с которыми его легко перепутать;
- разобрать, где решение нормальное, а где уже есть запахи.
Позже подключил ИИ как помощника именно для такой практики. Не чтобы он просто писал код за меня, а чтобы подкидывал задания, задавал новые вариации и помогал разбирать, где моя логика хромает. И вот тогда паттерны начали восприниматься не как абстрактная теория, а как повторяющиеся ходы, которые реально можно замечать в проектировании.
Для себя я из этого вынес простую мысль: паттерны нормально заходят не через чтение определений, а через многократное распознавание, сравнение и разбор.
В итоге я даже собрал для себя небольшой тренажерный формат, чтобы гонять такие упражнения регулярно. Не как “энциклопедию паттернов”, а именно как практику на узнавание, различение и разбор решений.
Вообще интересно, у кого как это было.
У вас паттерны тоже начали нормально укладываться только через практику?
Или, наоборот, книги по паттернам GoF сразу заходят без боли?
Как скачать PDF по URL в C#
В повседневной разработке нам часто нужно получать ресурсы из интернета, особенно PDF-документы. Будь то автоматическое резервное копирование онлайн-отчётов, пакетная загрузка электронных инвойсов или получение динамически сгенерированных файлов контрактов — умение эффективно и надёжно сохранять удалённые PDF-файлы локально является очень практичным навыком.
В этой статье объясняется, как использовать библиотеку Spire.PDF для .NET с C#, чтобы скачать PDF-документ по заданному URL и сохранить его локально. Spire.PDF предоставляет богатый набор возможностей обработки PDF, выходящий далеко за рамки простого скачивания и сохранения файлов.
Предварительные требования
Сначала нужно установить Spire.PDF для .NET в вашем проекте. Это можно сделать через консоль диспетчера пакетов NuGet:
Install-Package Spire.PDF
Или через .NET CLI:
dotnet add package Spire.PDF
Библиотека поддерживает .NET Framework 4.0 и выше, .NET Core 3.1, .NET 5.0 и более поздние версии.
Код реализации
Ниже приведён полный пример кода:
using System.IO;
using System.Net;
using Spire.Pdf;
namespace DownloadPdfFromUrl
{
class Program
{
static void Main(string[] args)
{
// Создаём объект PdfDocument
PdfDocument doc = new PdfDocument();
// Создаём объект WebClient для загрузки веб-ресурсов
WebClient webClient = new WebClient();
// Скачиваем данные PDF по URL в поток памяти (MemoryStream)
using (MemoryStream ms = new MemoryStream(
webClient.DownloadData("http://www.example.com/sample.pdf")))
{
// Загружаем данные PDF из потока в объект PdfDocument
doc.LoadFromStream(ms);
}
// Сохраняем PDF-документ в локальный файл
doc.SaveToFile("result.pdf", FileFormat.PDF);
// Освобождаем ресурсы
webClient.Dispose();
doc.Close();
}
}
}
Пояснение к коду
1. Создание объекта PdfDocument
PdfDocument — это основной класс Spire.PDF, представляющий экземпляр PDF-документа. Он используется для хранения и обработки PDF-данных, загруженных из интернета.
2. Использование WebClient для скачивания данных
WebClient — это простой класс загрузки по HTTP в .NET. Метод DownloadData возвращает byte[], который представляет собой исходное бинарное содержимое PDF-файла.
3. Использование MemoryStream как “моста”
Обёртка массива байт в MemoryStream позволяет использовать метод doc.LoadFromStream(ms). Это избавляет от неэффективного процесса сохранения файла на диск, а затем чтения его обратно — и даёт возможность обрабатывать данные в памяти.
4. Загрузка и сохранение PDF
Метод LoadFromStream анализирует поток памяти и формирует пригодный для использования PDF-документ. Затем метод SaveToFile сохраняет документ в локальное хранилище под именем файла result.pdf.
Примечания
Обработка исключений : В производственных средах рекомендуется добавлять блоки try-catch для обработки сетевых таймаутов, недействительных URL, ошибок формата PDF и других исключений.
Управление памятью : И WebClient, и PdfDocument реализуют интерфейс IDisposable, поэтому ресурсы должны быть корректно освобождены. В примере MemoryStream обрабатывается через using, но также рекомендуется явно освобождать webClient и doc или оборачивать их в блоки using .
Асинхронная версия : Для больших файлов рассмотрите использование WebClient.DownloadDataTaskAsync или переход на HttpClient с асинхронными методами, чтобы не блокировать UI-поток.
Корректность URL : Убедитесь, что URL напрямую указывает на PDF-файл, а не на страницу перенаправления.
Расширенные сценарии
С помощью Spire.PDF вы можете выполнять дополнительные операции сразу после загрузки PDF, например:
Извлечение текста или изображений
Объединение нескольких PDF-файлов
Добавление водяных знаков или колонтитулов (верхний/нижний)
Конвертация PDF в изображения или формат Word
Итог
В этой статье показано, как скачать PDF по URL и сохранить его локально с помощью C# и Spire.PDF для .NET. Весь процесс простой и эффективный — требуется всего несколько строк ключевого кода.
Spire.PDF — это не только инструмент для загрузки и сохранения документов, но и мощная библиотека для обработки PDF, которую стоит изучить дальше.
C#: Быстрая генерация документов Word с помощью шаблона
В повседневной разработке часто возникают сценарии, требующие массовой генерации договоров, уведомлений, отчётов и других документов Word. Самый элегантный способ — подготовить файл шаблона и через код заменить в нём плейсхолдеры, быстро получив итоговый документ. Сегодня поделимся, как легко реализовать эту задачу с помощью Free Spire.Doc for .NET.
Почему Free Spire.Doc?
Free Spire.Doc — это бесплатный и простой в использовании компонент для работы с Word, не требующий установки Microsoft Office. Он позволяет создавать, читать, редактировать и сохранять документы. Поддерживает .NET Framework и .NET Core, что идеально подходит для пакетной обработки на сервере.
Установка через NuGet:
PM> Install-Package FreeSpire.Doc
Реализация подхода
Заранее разработать шаблон Word (например, template.docx), отметив специальными плейсхолдерами места для заполнения
В коде загрузить шаблон и заменить плейсхолдеры на реальные данные
Поддерживается замена текста и вставка изображений (например, фотографий)
Сохранить результат как новый документ Word
Полный код
using Spire.Doc;
using Spire.Doc.Documents;
using Spire.Doc.Fields;
using System.Drawing;
namespace CreateWordByReplacingPlaceholders
{
class Program
{
static void Main(string[] args)
{
// Инициализация нового объекта Document
Document document = new Document();
// Загрузка файла шаблона Word
document.LoadFromFile("C:\\Users\\Administrator\\Desktop\\template.docx");
// Словарь для хранения плейсхолдеров и их замен
Dictionary<string, string> replaceDict = new Dictionary<string, string>
{
{ "#name#", "Ли Сы" },
{ "#gender#", "Мужской" },
{ "#birthdate#", "20 марта 1990 г." },
{ "#address#", "Улица Северного Сианя" },
{ "#city#", "Чэнду" },
{ "#province#", "Сычуань" },
{ "#postal#", "610000" },
{ "#country#", "Китай" }
};
// Замена плейсхолдеров в документе соответствующими значениями
foreach (KeyValuePair<string, string> kvp in replaceDict)
{
document.Replace(kvp.Key, kvp.Value, true, true);
}
// Путь к файлу изображения
String imagePath = "C:\\Users\\Administrator\\Desktop\\portrait.png";
// Замена плейсхолдера для фотографии на изображение
ReplaceTextWithImage(document, "#photo#", imagePath);
// Сохранение изменённого документа
document.SaveToFile("ReplacePlaceholders.docx", FileFormat.Docx);
// Освобождение ресурсов
document.Dispose();
}
// Метод для замены плейсхолдера в документе на изображение
static void ReplaceTextWithImage(Document document, String stringToReplace, String imagePath)
{
// Загрузка изображения по указанному пути
Image image = Image.FromFile(imagePath);
DocPicture pic = new DocPicture(document);
pic.LoadImage(image);
pic.Width = 130;
// Поиск плейсхолдера в документе
TextSelection selection = document.FindString(stringToReplace, false, true);
// Получение диапазона найденного текста
TextRange range = selection.GetAsOneRange();
int index = range.OwnerParagraph.ChildObjects.IndexOf(range);
// Вставка изображения и удаление текста-плейсхолдера
range.OwnerParagraph.ChildObjects.Insert(index, pic);
range.OwnerParagraph.ChildObjects.Remove(range);
}
}
}
Подробное объяснение кода
1. Замена текста
Сначала подготавливается словарь, хранящий соответствие между плейсхолдерами и заменяющим текстом:
Dictionary<string, string> replaceDict = new Dictionary<string, string>
{
{ "#name#", "Ли Сы" },
{ "#gender#", "Мужской" },
// ... остальные поля
};
Затем происходит перебор словаря и вызов метода document.Replace. Последние два параметра указывают, учитывать ли регистр и заменять ли только целые слова.
2. Замена на изображение
Замена на изображение немного сложнее. Основные шаги:
Загрузка изображения с помощью Image.FromFile
Создание объекта DocPicture, загрузка в него изображения и установка ширины
Поиск плейсхолдера через FindString
Получение абзаца и индекса, где находится плейсхолдер
Вставка изображения в ту же позицию и удаление текста-плейсхолдера
3. Сохранение документа
В конце вызывается метод SaveToFile для сохранения нового документа и освобождаются ресурсы.
Рекомендации по подготовке шаблона
В шаблоне Word отметьте места для динамической замены с помощью плейсхолдеров, например:
Важные замечания при использовании
Убедитесь, что пути к файлу шаблона и изображению указаны верно
Для плейсхолдеров рекомендуется использовать уникальные метки (например, #имя_поля#), чтобы избежать случайной замены
При замене на изображение можно настроить свойства Width и Height для контроля размера отображения
После обработки не забудьте вызвать Dispose() для освобождения ресурсов
Заключение
С помощью Free Spire.Doc достаточно поддерживать один набор шаблонных файлов, чтобы быстро генерировать тысячи персонализированных документов, значительно повышая эффективность работы. Этот компонент также поддерживает объединение ячеек, настройку стилей шрифта, добавление колонтитулов и другие расширенные функции. Заинтересовавшиеся могут глубже изучить его возможности!











