كيفية تحويل HTML إلى PDF باستخدام Azure Functions وwkhtmltopdf
مقدمة: لماذا نحتاج إلى تحويل HTML إلى PDF؟
يُعد إنشاء ملفات PDF تلقائياً من صفحات HTML من المهام الشائعة في الأنظمة الحديثة، خاصة عند بناء تطبيقات الأعمال والخدمات السحابية. فغالباً ما تحتاج الشركات إلى إنتاج فواتير، وتقارير طبية، ونماذج تأمين، وعروض أسعار، ومستندات رسمية قابلة للطباعة والأرشفة.
هناك أكثر من طريقة لتحقيق ذلك، لكن ليست كلها عملية على مستوى الإنتاج:
- استخدام أدوات تحرير يدوية مثل أدوات التعبئة والتوقيع، وهي مناسبة للحالات الفردية فقط.
- إنشاء ملف
PDFمباشرة من التطبيق، وهي طريقة مقبولة للمستندات البسيطة جداً. - الاعتماد على أداة متخصصة مثل
wkhtmltopdfلتحويلHTMLإلىPDFبدقة ومرونة أعلى.
تُعد wkhtmltopdf خياراً ممتازاً لأنها مجانية، مفتوحة المصدر، وتعمل على منصات متعددة. وعند دمجها مع Azure Functions يمكننا بناء حل سحابي مرن وقابل للتوسع دون تحميل الخادم الرئيسي عبئاً إضافياً.

المتطلبات الأساسية قبل البدء
قبل تنفيذ المشروع، تأكد من توفر العناصر التالية:
- محرر
VS Codeمثبت على جهازك. - حساب فعّال على
Azure Portal. - خطة
Linux Basic (B1) App Service Plan، ويمكن أيضاً استخدامWindows Basic (B1)إن كانت متوفرة. - حساب
Azure Storageلتخزين ملفاتPDFالناتجة. - معرفة أساسية بنظام
Linuxوأوامر الطرفية.
لماذا نستخدم Azure Functions لهذه المهمة؟
عملية تحويل HTML إلى PDF ليست لحظية دائماً، بل قد تستهلك وقتاً وموارد، خاصة إذا كان المستند كبيراً أو يحتوي على تنسيقات معقدة. لذلك لا يُنصح بتنفيذها على الخادم الرئيسي المسؤول عن استقبال طلبات المستخدمين، لأن ذلك قد يسبب بطئاً في الاستجابة أو يؤثر في العمليات الأهم.
هنا تظهر قيمة Azure Functions، إذ تسمح لك بعزل هذه المهمة داخل وظيفة مستقلة تعمل عند الطلب عبر HTTP Trigger أو أي نوع آخر من المشغلات. هذا الأسلوب يوفر:
- قابلية توسع أفضل.
- عزلاً للمهام الثقيلة عن التطبيق الرئيسي.
- خفضاً في التكلفة مقارنة ببناء خدمة منفصلة كاملة.
- مرونة في الربط مع
Azure Storageوخدمات المراقبة.
إنشاء مشروع Azure Functions محلياً
ابدأ أولاً بتثبيت أدوات Azure Functions Core Tools بحسب نظام التشغيل لديك. بعد الانتهاء، افتح سطر الأوامر ونفّذ الأمر التالي:
func init html2pdf
اسم المشروع هنا هو html2pdf، ويمكنك تغييره إلى أي اسم مناسب.
بعد تشغيل الأمر سيُطلب منك اختيار بيئة التشغيل worker runtime. اختر dotnet لأنه مناسب لهذا المشروع ويوفّر تكاملاً ممتازاً مع تقنيات مايكروسوفت.
سينشئ ذلك مجلداً باسم html2pdf داخل المسار الحالي. بعد ذلك افتح المشروع باستخدام VS Code، لأننا سنستفيد منه لاحقاً في النشر المباشر إلى Azure Functions.
إنشاء ملف الوظيفة Html2Pdf.cs
داخل المشروع، أنشئ ملفاً جديداً باسم Html2Pdf.cs. سنبدأ ببناء هيكل أولي لوظيفة تعمل عبر HTTP POST.
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
namespace Html2Pdf
{
public class Html2Pdf
{
// The name of the function
[FunctionName("Html2Pdf")]
// The first arugment tells that the functions can be triggerd by a POST HTTP request.
// The second argument is mainly used for logging information, warnings or errors
public void Run([HttpTrigger(AuthorizationLevel.Function, "POST")] Html2PdfRequest Request, ILogger Log)
{
}
}
}
في هذا الهيكل:
- تحدد السمة
[FunctionName("Html2Pdf")]اسم الوظيفة داخلAzure. - المعامل
[HttpTrigger(AuthorizationLevel.Function, "POST")]يعني أن الوظيفة تُستدعى عبر طلبPOST. - الكائن
ILoggerمفيد لتسجيل الأحداث والأخطاء أثناء التشغيل.
إنشاء نموذج الطلب Html2PdfRequest
بما أن الوظيفة تستقبل محتوى HTML واسم الملف، نحتاج إلى إنشاء نموذج بيانات يمثل الطلب. أضف ملفاً باسم Html2PdfRequest.cs وضع فيه الكود التالي:
namespace Html2Pdf
{
public class Html2PdfRequest
{
// The HTML content that needs to be converted.
public string HtmlContent { get; set; }
// The name of the PDF file to be generated
public string PDFFileName { get; set; }
}
}
هذا النموذج بسيط لكنه مهم، لأنه يحدد البيانات المطلوبة لإنشاء الملف:
HtmlContent: المحتوى المراد تحويله.PDFFileName: اسم ملفPDFالناتج.
إضافة مكتبة DinkToPdf إلى المشروع
حتى نتمكن من استدعاء wkhtmltopdf من كود .NET المُدار، نستخدم غلافاً برمجياً مناسباً. من أفضل الخيارات هنا مكتبة DinkToPdf، وهي تعتمد على P/Invoke للوصول إلى المكتبات غير المُدارة.
يمكنك إضافتها عبر NuGet باستخدام الأمر التالي من جذر المشروع:
dotnet add package DinkToPdf --version 1.0.8
ميزة هذه المكتبة أنها تخفي كثيراً من التفاصيل التقنية المعقدة، وتمنحك واجهة أبسط لتحويل المحتوى إلى PDF.
بناء دالة تحويل HTML إلى PDF
الآن أضف داخل الفئة Html2Pdf الكود التالي لإنشاء دالة مسؤولة عن تحويل المحتوى:
// Read more about converter on: https://github.com/rdvojmoc/DinkToPdf
// For our purposes we are going to use SynchronizedConverter
IPdfConverter pdfConverter = new SynchronizedConverter(new PdfTools());
// A function to convert html content to pdf based on the configuration passed as arguments
// Arguments:
// HtmlContent: the html content to be converted
// Width: the width of the pdf to be created. e.g. "8.5in", "21.59cm" etc.
// Height: the height of the pdf to be created. e.g. "11in", "27.94cm" etc.
// Margins: the margis around the content
// DPI: The dpi is very important when you want to print the pdf.
// Returns a byte array of the pdf which can be stored as a file
private byte[] BuildPdf(string HtmlContent, string Width, string Height, MarginSettings Margins, int? DPI = 180)
{
// Call the Convert method of SynchronizedConverter "pdfConverter"
return pdfConverter.Convert(
new HtmlToPdfDocument()
{
// Set the html content
Objects = { new ObjectSettings { HtmlContent = HtmlContent } },
// Set the configurations
GlobalSettings = new GlobalSettings
{
// PaperKind.A4 can also be used instead
PaperSize = new PechkinPaperSize(Width, Height),
DPI = DPI,
Margins = Margins
}
});
}
تعيد هذه الدالة مصفوفة من النوع byte[] تمثل ملف PDF في الذاكرة، وهو ما يسهّل رفعه مباشرة إلى التخزين السحابي أو إرساله إلى خدمة أخرى.
أهم الإعدادات التي يمكنك تعديلها هنا:
- أبعاد الصفحة مثل
8.5inو11in. - الدقة
DPI، وهي مؤثرة جداً في جودة الطباعة. - الهوامش عبر
MarginSettings.
استدعاء دالة التحويل داخل Run
بعد بناء دالة التحويل، يمكن استدعاؤها من داخل الوظيفة الرئيسية:
// PDFByteArray is a byte array of pdf generated from the HtmlContent
var PDFByteArray = BuildPdf(Request.HtmlContent, "8.5in", "11in", new MarginSettings(0, 0, 0, 0));
هنا يتم استخدام محتوى HTML القادم في الطلب وتحويله إلى ملف PDF بمقاس مناسب ودون هوامش.
رفع ملف PDF إلى Azure Storage
بعد إنشاء الملف في الذاكرة، تأتي الخطوة التالية وهي حفظه داخل حاوية Blob في Azure Storage. تأكد أولاً من إنشاء container مناسب، ثم أضف الكود التالي:
// The connection string of the Storage Account to which our PDF file will be uploaded
// Make sure to replace with your connection string.
var StorageConnectionString = "DefaultEndpointsProtocol=https;AccountName=<YOUR ACCOUNT NAME>;AccountKey=<YOUR ACCOUNT KEY>;EndpointSuffix=core.windows.net";
// Generate an instance of CloudStorageAccount by parsing the connection string
var StorageAccount = CloudStorageAccount.Parse(StorageConnectionString);
// Create an instance of CloudBlobClient to connect to our storage account
CloudBlobClient BlobClient = StorageAccount.CreateCloudBlobClient();
// Get the instance of CloudBlobContainer which points to a container name "pdf"
// Replace your own container name
CloudBlobContainer BlobContainer = BlobClient.GetContainerReference("pdf");
// Get the instance of the CloudBlockBlob to which the PDFByteArray will be uploaded
CloudBlockBlob Blob = BlobContainer.GetBlockBlobReference(Request.PDFFileName);
// Upload the pdf blob
await Blob.UploadFromByteArrayAsync(PDFByteArray, 0, PDFByteArray.Length);
هذه الخطوة تجعل الوظيفة أكثر عملية، لأن المستند الناتج لن يبقى في الذاكرة فقط، بل سيُحفظ في التخزين السحابي ليكون جاهزاً للتنزيل أو الأرشفة أو الإرسال لاحقاً.
بعد إضافة هذا الجزء ستلاحظ أنك بحاجة إلى:
- إضافة تعليمات
usingالناقصة. - تغيير نوع إرجاع الدالة
Runمنvoidإلىasync Task.
النسخة النهائية من ملف Html2Pdf.cs
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using DinkToPdf;
using IPdfConverter = DinkToPdf.Contracts.IConverter;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Threading.Tasks;
namespace Html2Pdf
{
public class Html2Pdf
{
// Read more about converter on: https://github.com/rdvojmoc/DinkToPdf
// For our purposes we are going to use SynchronizedConverter
IPdfConverter pdfConverter = new SynchronizedConverter(new PdfTools());
// A function to convert html content to pdf based on the configuration passed as arguments
// Arguments:
// HtmlContent: the html content to be converted
// Width: the width of the pdf to be created. e.g. "8.5in", "21.59cm" etc.
// Height: the height of the pdf to be created. e.g. "11in", "27.94cm" etc.
// Margins: the margis around the content
// DPI: The dpi is very important when you want to print the pdf.
// Returns a byte array of the pdf which can be stored as a file
private byte[] BuildPdf(string HtmlContent, string Width, string Height, MarginSettings Margins, int? DPI = 180)
{
// Call the Convert method of SynchronizedConverter "pdfConverter"
return pdfConverter.Convert(
new HtmlToPdfDocument()
{
// Set the html content
Objects = { new ObjectSettings { HtmlContent = HtmlContent } },
// Set the configurations
GlobalSettings = new GlobalSettings
{
// PaperKind.A4 can also be used instead of width & height
PaperSize = new PechkinPaperSize(Width, Height),
DPI = DPI,
Margins = Margins
}
});
}
// The name of the function
[FunctionName("Html2Pdf")]
// The first arugment tells that the functions can be triggerd by a POST HTTP request.
// The second argument is mainly used for logging information, warnings or errors
public async Task Run([HttpTrigger(AuthorizationLevel.Function, "POST")] Html2PdfRequest Request, ILogger Log)
{
// PDFByteArray is a byte array of pdf generated from the HtmlContent
var PDFByteArray = BuildPdf(Request.HtmlContent, "8.5in", "11in", new MarginSettings(0, 0, 0, 0));
// The connection string of the Storage Account to which our PDF file will be uploaded
var StorageConnectionString = "DefaultEndpointsProtocol=https;AccountName=<YOUR ACCOUNT NAME>;AccountKey=<YOUR ACCOUNT KEY>;EndpointSuffix=core.windows.net";
// Generate an instance of CloudStorageAccount by parsing the connection string
var StorageAccount = CloudStorageAccount.Parse(StorageConnectionString);
// Create an instance of CloudBlobClient to connect to our storage account
CloudBlobClient BlobClient = StorageAccount.CreateCloudBlobClient();
// Get the instance of CloudBlobContainer which points to a container name "pdf"
// Replace your own container name
CloudBlobContainer BlobContainer = BlobClient.GetContainerReference("pdf");
// Get the instance of the CloudBlockBlob to which the PDFByteArray will be uploaded
CloudBlockBlob Blob = BlobContainer.GetBlockBlobReference(Request.PDFFileName);
// Upload the pdf blob
await Blob.UploadFromByteArrayAsync(PDFByteArray, 0, PDFByteArray.Length);
}
}
}
إضافة مكتبة wkhtmltopdf إلى المشروع
حتى تعمل مكتبة DinkToPdf فعلياً، لا بد من توفير المكتبة الثنائية الخاصة بـ wkhtmltopdf داخل المشروع. وهنا يجب الانتباه إلى نوع النظام المستهدف في بيئة Azure App Service Plan.
في هذا السيناريو تم اختيار Linux Basic (B1) لأنه أوفر تكلفة من الخطة المكافئة على Windows. أثناء إعداد هذا الدليل كانت بيئة Azure App Service تعمل على Debian 10 بمعمارية amd64.
توفر DinkToPdf مكتبات مُجمّعة مسبقاً لأنظمة متعددة:
libwkhtmltox.soلنظامLinux.libwkhtmltox.dllلنظامWindows.libwkhtmltox.dylibلنظامMacOS.
بالنسبة لخطة Linux، حمّل الملف libwkhtmltox.so وضعه في جذر المشروع.

تحديث ملف csproj لنسخ المكتبة عند البناء والنشر
للتأكد من تضمين ملف .so ضمن مخرجات البناء والنشر، افتح ملف csproj وأضف العنصر التالي داخل ItemGroup:
<None Update="./libwkhtmltox.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</None>
وإذا أردت رؤية الملف كاملاً بعد التحديث، فسيكون كالتالي:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DinkToPdf" Version="1.0.8" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.11" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
<None Update="./libwkhtmltox.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
إنشاء مورد Azure Functions App من البوابة
قبل النشر، عليك إنشاء تطبيق الوظائف من خلال Azure Portal. أثناء الإعداد، احرص على اختيار خطة لا تقل عن Basic، مع تحديد نظام التشغيل Linux.


من الأفضل أيضاً تفعيل Application Insights لأنه يساعدك على تتبع السجلات والأداء واكتشاف الأخطاء بسرعة، وتكلفته غالباً منخفضة جداً.

بعد ذلك تابع إلى Tags ثم أنشئ المورد. قد تستغرق العملية بضع دقائق.
نشر المشروع إلى Azure Functions عبر VS Code
بعد إنشاء المورد، يمكنك نشر المشروع مباشرة من VS Code. ثبّت إضافة Azure Functions من سوق الإضافات أولاً.

بعد التثبيت، سيظهر رمز Azure في الشريط الجانبي. انقر عليه ثم سجّل الدخول إلى حسابك.

بعد تسجيل الدخول ستظهر لك قائمة تطبيقات الوظائف المتاحة:

للنشر:
- اضغط
F1. - اختر
Azure Functions: Deploy to Function App.... - حدد التطبيق الذي أنشأته حديثاً.
- وافق على رسالة التأكيد وابدأ النشر.
قد تستغرق عملية النشر عدة دقائق حسب حجم المشروع والاتصال.
تهيئة wkhtmltopdf داخل بيئة Azure
بعد النشر، تبقى خطوة أخيرة مهمة: نقل ملف libwkhtmltox.so إلى الموقع الصحيح داخل تطبيق الوظائف.
انتقل إلى Azure Portal ثم افتح تطبيق الوظائف، وابحث في الشريط الجانبي عن SSH وافتحه.

بعد فتح نافذة SSH، انتقل إلى مجلد bin بالأمر التالي:
cd /home/site/wwwroot/bin
عند تنفيذ الأمر ls قد لا تجد الملف libwkhtmltox.so داخل هذا المجلد، لأنه غالباً موجود في المسار /home/site/wwwroot. لذا انسخه إلى bin باستخدام الأمر التالي:
cp ../libwkhtmltox.so libwkhtmltox.so
بعد هذه الخطوة تصبح المكتبة في الموقع الذي تحتاجه الوظيفة أثناء التشغيل.
كيفية استدعاء الوظيفة واختبارها
قبل اختبار الوظيفة، تحتاج إلى الحصول على المفتاح السري Code الخاص بها، وهو مطلوب لاستدعائها بشكل آمن.
للحصول عليه:
- افتح
Azure Portal. - انتقل إلى تطبيق الوظائف الخاص بك.
- من القائمة الجانبية ابحث عن
Functions. - اختر الوظيفة
Html2Pdf. - افتح قسم
Function Keys. - انسخ المفتاح الافتراضي.


الآن يمكنك اختبار الوظيفة من خلال تطبيق Console بسيط في .NET باستخدام الكود التالي:
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Demo.ConsoleApp
{
public class Program
{
public static async Task Main(string[] args)
{
string AzureFunctionsUrl = "https://<Your Base Url>/api/Html2Pdf?code=<Replace with your Code>";
using (HttpClient client = new HttpClient())
{
var Request = new Html2PdfRequest
{
HtmlContent = "<h1>Hello World</h1>",
PDFFileName = "hello-world.pdf"
};
string json = JsonConvert.SerializeObject(Request);
var buffer = System.Text.Encoding.UTF8.GetBytes(json);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (HttpResponseMessage res = await client.PostAsync(AzureFunctionsUrl, byteContent))
{
if (res.StatusCode != HttpStatusCode.NoContent)
{
throw new Exception("There was an error uploading the pdf");
}
}
}
}
}
public class Html2PdfRequest
{
// The HTML content that needs to be converted.
public string HtmlContent { get; set; }
// The name of the PDF file to be generated
public string PDFFileName { get; set; }
}
}
بعد استبدال عنوان الخدمة والمفتاح الصحيحين وتشغيل التطبيق، سيتم إنشاء ملف باسم hello-world.pdf داخل الحاوية pdf في Azure Storage.
ملاحظات تقنية مهمة لتحسين الاعتمادية
رغم أن المثال السابق عملي وواضح، فإن بيئة الإنتاج تتطلب بعض التحسينات الإضافية، مثل:
- التحقق من صحة المدخلات قبل بدء التحويل، خاصة محتوى
HtmlContentواسم الملف. - إضافة معالجة أخطاء باستخدام كتل
try/catch. - تخزين
Storage Connection Stringداخل إعدادات التطبيق بدلاً من تضمينه في الكود. - تسجيل الأحداث عبر
Application Insightsلتسهيل التشخيص. - تقييد حجم المحتوى المرسل للوظيفة لتفادي الاستهلاك المفرط للموارد.
هذه الممارسات تعزز الأمان والاستقرار، وتساعد على تشغيل الخدمة بكفاءة في البيئات الحقيقية.
مقارنة سريعة بين الخيارات المتاحة
| الطريقة | المزايا | القيود |
|---|---|---|
| الأدوات اليدوية | سهلة للحالات الفردية | غير قابلة للتوسع |
إنشاء PDF مباشرة من التطبيق |
مناسب للمستندات البسيطة | مرونة أقل في التنسيق المعقد |
wkhtmltopdf مع Azure Functions |
مرن، اقتصادي، وقابل للتوسع | يتطلب إعداداً أولياً أدق |
الخاتمة
يوفر الدمج بين Azure Functions وwkhtmltopdf حلاً قوياً لتحويل HTML إلى PDF في البيئات السحابية. صحيح أن الإعداد الأولي يحتاج إلى بعض العناية، خاصة فيما يتعلق بالمكتبات الثنائية ومسارات النشر، لكنه في المقابل يمنحك حلاً منخفض التكلفة ومرناً وقابلاً للتشغيل دون خادم تقليدي دائم.
إذا كنت تبني نظاماً يحتاج إلى إنشاء مستندات تلقائياً، فهذه المقاربة مناسبة جداً، خصوصاً عندما تريد فصل المهام الثقيلة عن التطبيق الأساسي مع الاستفادة من خدمات Azure الأخرى مثل التخزين والمراقبة.
الخلاصة التقنية
من الناحية التقنية، يُعد استخدام Azure Functions مع DinkToPdf وwkhtmltopdf خياراً عملياً لإنشاء مستندات PDF عند الطلب دون الحاجة إلى خادم مخصص دائم التشغيل. التحدي الأكبر ليس في كتابة الكود، بل في ضبط بيئة التشغيل وموضع المكتبات الأصلية بشكل صحيح. وعند تنفيذ هذا الإعداد بعناية، تحصل على حل سحابي اقتصادي ومرن يصلح لتطبيقات الفوترة والتقارير والأرشفة الرقمية على نطاق جيد.