A portable .NET library for converting Markdown, HTML, and plain text to PDF using embedded wkhtmltopdf. Windows-only, no external dependencies required.
$ dotnet add package MarkdownToPdf.NETA portable .NET library for converting Markdown, HTML, and plain text to PDF using embedded wkhtmltopdf. Windows-only, with no external dependencies required.
Install the package via NuGet Package Manager:
dotnet add package MarkdownToPdf.NET
Or via Package Manager Console:
Install-Package MarkdownToPdf.NETNote: Version 2.0+ requires .NET 6.0 or later and Windows operating system.
using PdfTools.Services;
using PdfTools.Models;
using Microsoft.Extensions.Logging;
// Create logger (or use DI)
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<PdfGenerator>();
// Create generator instance
var generator = new PdfGenerator(logger);
// Generate PDF from Markdown
string markdown = @"
# My Document
This is a **sample** document with *formatting*.
## Features
- Beautiful styling
- Easy to use
- Professional output
";
await generator.GeneratePdfFromMarkdownAsync(markdown, "output.pdf");var options = new PdfOptions
{
Title = "My Custom Document",
Author = "John Doe",
Subject = "Sample PDF Generation",
PageSize = "A4",
Orientation = "Portrait",
Margins = new PdfMargins
{
Top = 25,
Bottom = 25,
Left = 20,
Right = 20
},
UseEnhancedStyling = true,
CustomCss = @"
body { font-family: 'Arial'; }
h1 { color: #ff6b6b; }
"
};
await generator.GeneratePdfFromMarkdownAsync(markdown, "custom-output.pdf", options);// Get PDF as byte array (useful for web applications)
var pdfBytes = await generator.GeneratePdfBytesFromMarkdownAsync(markdown, options);
// In ASP.NET Controller
return File(pdfBytes, "application/pdf", "document.pdf");// Convert HTML to PDF
string html = "<h1>Hello World</h1><p>This is HTML content.</p>";
await generator.GeneratePdfFromHtmlAsync(html, "html-output.pdf", options);
// Convert plain text to PDF
string text = "This is plain text that will be converted to a nicely formatted PDF.";
await generator.GeneratePdfFromTextAsync(text, "text-output.pdf", options);using PdfTools.Extensions;
// In Program.cs
builder.Services.AddPdfToolsV2();
// Or with custom logging configuration
builder.Services.AddPdfToolsV2(logging =>
{
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Information);
});[ApiController]
[Route("api/[controller]")]
public class PdfController : ControllerBase
{
private readonly IPdfGenerator _pdfGenerator;
private readonly ILogger<PdfController> _logger;
public PdfController(IPdfGenerator pdfGenerator, ILogger<PdfController> logger)
{
_pdfGenerator = pdfGenerator;
_logger = logger;
}
[HttpPost("from-markdown")]
public async Task<IActionResult> GenerateFromMarkdown(
[FromBody] MarkdownRequest request,
CancellationToken cancellationToken)
{
try
{
var options = new PdfOptions
{
Title = request.Title,
Author = request.Author,
UseEnhancedStyling = true
};
var pdfBytes = await _pdfGenerator.GeneratePdfBytesFromMarkdownAsync(
request.Markdown, options, cancellationToken);
return File(pdfBytes, "application/pdf", "document.pdf");
}
catch (PdfGenerationException ex)
{
_logger.LogError(ex, "PDF generation failed");
return BadRequest($"PDF generation failed: {ex.Message}");
}
}
}
public class MarkdownRequest
{
public string Markdown { get; set; } = string.Empty;
public string? Title { get; set; }
public string? Author { get; set; }
}public class PdfOptions
{
public string? Title { get; set; } // PDF metadata
public string? Author { get; set; } // PDF metadata
public string? Subject { get; set; } // PDF metadata
public string Orientation { get; set; } // "Portrait" or "Landscape"
public string PageSize { get; set; } // "A4", "Letter", "Legal", etc.
public string? CustomCss { get; set; } // Custom CSS styles
public PdfMargins Margins { get; set; } // Page margins
public bool UseEnhancedStyling { get; set; } // Use enhanced CSS
public int Dpi { get; set; } // Output DPI (default: 200)
public bool EnableJavaScript { get; set; } // Enable JS execution
public bool PrintBackground { get; set; } // Print background colors
public int TimeoutMs { get; set; } // Page load timeout
}The library provides specific exception types:
try
{
await generator.GeneratePdfFromMarkdownAsync(markdown, outputPath);
}
catch (InvalidInputException ex)
{
// Handle invalid input parameters
Console.WriteLine($"Invalid input: {ex.Message}");
}
catch (PdfConversionException ex)
{
// Handle PDF conversion errors
Console.WriteLine($"Conversion failed: {ex.Message}");
Console.WriteLine($"wkhtmltopdf stderr: {ex.StandardError}");
Console.WriteLine($"Exit code: {ex.ExitCode}");
}
catch (WkhtmltopdfNotFoundException ex)
{
// Handle missing wkhtmltopdf binaries
Console.WriteLine($"Binary extraction failed: {ex.Message}");
}
catch (FileOperationException ex)
{
// Handle file I/O errors
Console.WriteLine($"File operation failed: {ex.Message}");
}The library provides detailed logging at each step:
// Configure logging to see conversion details
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug); // For detailed logs
});
var logger = loggerFactory.CreateLogger<PdfGenerator>();
var generator = new PdfGenerator(logger);
// Logs will show:
// - Conversion start/end times
// - Binary extraction process
// - wkhtmltopdf command arguments
// - PDF file size and generation time
// - Any errors with full details| Feature | v1.x (DinkToPdf) | v2.0 (Direct wkhtmltopdf) |
|---|---|---|
| Platform Support | Windows, Linux, macOS | Windows only |
| Dependencies | DinkToPdf wrapper | Direct executable |
| API Style | Synchronous | Asynchronous |
| Error Handling | Generic exceptions | Specific exception types |
| Logging | Basic | Comprehensive step-by-step |
| Package Size | ~15MB (multi-platform) | ~8MB (Windows-only) |
| Performance | Good | Better (direct execution) |
| Control | Limited | Full wkhtmltopdf options |
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.