DEV Community

TemplateMaster
TemplateMaster

Posted on

Generate PDFs and Emails in ASP.NET Core with the TemplateMaster NuGet Package

If your ASP.NET Core app needs to produce invoices, contracts or transactional emails, you usually end up choosing between a PDF library you template by hand and a separate SaaS you call over HTTP. TemplateMaster offers a third option: a NuGet package, TemplateMaster.AspNetCore, that embeds a visual template editor, its API and a C# service inside your own application.

Your users stay your users, your data stays in your database, and generating a PDF becomes a method call instead of an HTTP request.

📚 Source: official documentation — .NET Integration (NuGet)


🧭 What you get

Package TemplateMaster.AspNetCore
Platform .NET 8 · ASP.NET Core
Editor Visual template editor served under /templatemaster (UI bundled, no Node.js needed)
C# API ITemplateMaster — generate PDFs and send emails straight from your code
Auth Reuses your app's authentication — there's no separate TemplateMaster login

📦 Prerequisites

  • .NET 8 and an ASP.NET Core application
  • SQL Server 2019+: a dedicated database for TemplateMaster (created automatically in development)
  • RabbitMQ in production (in development, UseInMemoryMessaging() is enough)
  • Cookie-based authentication in your app (Identity, OpenID Connect, cookie…). TemplateMaster maps your signed-in users to its own users; it doesn't ship its own sign-in page.

1️⃣ Install the package

dotnet add package TemplateMaster.AspNetCore
Enter fullscreen mode Exit fullscreen mode

That's the only package to reference. The whole TemplateMaster layer is included; only third-party dependencies (EF Core, MassTransit, PuppeteerSharp…) are pulled from nuget.org.


2️⃣ Configure appsettings.json

TemplateMaster reads its settings from a dedicated TemplateMaster section and never touches your other keys.

{
  "ConnectionStrings": {
    "TemplateMaster": "Server=localhost;Database=TemplateMaster;Trusted_Connection=True;TrustServerCertificate=True"
  },
  "RabbitMq": {
    "Host": "localhost",
    "Username": "guest",
    "Password": "guest"
  },
  "TemplateMaster": {
    "LicenseKey": "<your license key - keep it in user secrets / environment variables>",
    "PathRoot": "D:/templatemaster/files",
    "EmailSettings": {
      "SMTPSetting": { "Host": "smtp.example.com" },
      "Port": 587,
      "UserName": "noreply@example.com"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

PathRoot is where generated documents are stored on disk (it's printed at startup).


3️⃣ Wire it up in Program.cs

using TemplateMaster;

var builder = WebApplication.CreateBuilder(args);

// Your application's own authentication (cookie, Identity, OpenID Connect...)
builder.Services.AddAuthentication(/* ... */);
builder.Services.AddAuthorization(options =>
    options.AddPolicy("TemplateMasterAdmin", policy => policy.RequireRole("Admin")));

builder.Services
    .AddTemplateMaster(options =>
    {
        options.BasePath = "/templatemaster";
        options.Database.AutoCreate = builder.Environment.IsDevelopment();
    })
    .UseSqlServer(builder.Configuration.GetConnectionString("TemplateMaster")!)
    .UseRabbitMq(options =>
    {
        options.Host = builder.Configuration["RabbitMq:Host"]!;
        options.Username = builder.Configuration["RabbitMq:Username"]!;
        options.Password = builder.Configuration["RabbitMq:Password"]!;
    })
    .AddLicenseKey(licenseKey: builder.Configuration["TemplateMaster:LicenseKey"]);

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// TemplateMaster editor + API, reserved to your administrators
app.MapTemplateMaster("/templatemaster")
   .RequireAuthorization("TemplateMasterAdmin");

app.Run();
Enter fullscreen mode Exit fullscreen mode

Three things worth understanding here:

  • Database.AutoCreate creates the database on first run. Keep it for development only; in production, deploy the schema with the SQL scripts.
  • The dashboard always requires a signed-in user. RequireAuthorization(...) lets you add your own rules on top, like the Admin role policy above.
  • Messaging: UseRabbitMq is for production. For local development, UseInMemoryMessaging() saves you from running a broker.

4️⃣ Activate your license

Without a license, generated PDFs and emails carry a watermark and the free-plan limits apply: 10 documents and 10 emails per month. That's plenty to try everything out.

The key goes through AddLicenseKey, matching the TemplateMaster:LicenseKey entry of your configuration. Since it's a secret, keep it out of source control:

# Development: user secrets
dotnet user-secrets set "TemplateMaster:LicenseKey" "<your-key>"

# Production: environment variable
TemplateMaster__LicenseKey=<your-key>
Enter fullscreen mode Exit fullscreen mode

At startup, the result is written to your logs:

info: TemplateMaster.License[0]
      TemplateMaster license: VALID - client acme, issued 2026-10-03, expires 2027-10-03.
Enter fullscreen mode Exit fullscreen mode

If the key is rejected, the log explains why, but an invalid key never prevents your app from starting.


5️⃣ Open the editor

Run the app, sign in with your application's login, and go to /templatemaster. A default invoice template and a default email template are already created for you.

👥 Users and organizations

There's no TemplateMaster login screen. A user signed in to your app is automatically matched (by email) to a TemplateMaster user and organization, created on their first visit. Two models:

  • One organization (default): all your users share the same templates.
  • One organization per customer (Tenancy.Mode = HostManaged + TenantKeySelector): each customer of your app gets isolated templates and documents, exactly like on the SaaS platform. Perfect for B2B multi-tenant apps.

6️⃣ Generate PDFs and emails from C

Inject ITemplateMaster into your services. Every call applies the same rules as the REST API: validation, per-organization isolation, plan limits and document history. No HTTP call involved.

Method Purpose
GetTemplatesAsync(type?) List the organization's templates (code, name, type)
RenderHtmlAsync(code, model) Render a template to HTML with your data, without saving anything
GeneratePdfAsync(code, model, fileName?) Generate a PDF immediately and add it to the document history
QueueDocumentAsync(code, model) Queue a PDF generation, processed in the background
SendEmailAsync(email) Send an email from an email template
ForUser(user) Same API, on behalf of the signed-in user (their organization, their role)
ForTenant(tenantKey) Same API, for a given customer (one-organization-per-customer mode)

Here's an invoice service:

using TemplateMaster;

public sealed class InvoiceService(ITemplateMaster templateMaster)
{
    public async Task<byte[]> CreatePdfAsync(Invoice invoice, CancellationToken ct)
    {
        var template = (await templateMaster.GetTemplatesAsync(TemplateMasterTemplateType.Document, ct))
            .Single(t => t.Name == "invoice");

        return await templateMaster.GeneratePdfAsync(
            template.Code,
            new
            {
                invoice_number = invoice.Number,
                customer_name = invoice.CustomerName,
                items = invoice.Lines.Select(l => new { description = l.Label, quantity = l.Quantity })
            },
            fileName: $"{invoice.Number}.pdf",
            cancellationToken: ct);
    }
}
Enter fullscreen mode Exit fullscreen mode

And expose it with a minimal API endpoint:

app.MapGet("/invoices/{id:int}/pdf", async (int id, InvoiceService invoices, CancellationToken ct) =>
{
    var pdf = await invoices.CreatePdfAsync(await LoadInvoiceAsync(id, ct), ct);
    return Results.File(pdf, "application/pdf", $"invoice-{id}.pdf");
}).RequireAuthorization();
Enter fullscreen mode Exit fullscreen mode

⚠️ Model property names must match the template variables. {{invoice_number}} expects a property called invoice_number. Use an anonymous object, a dictionary, or [JsonPropertyName] to control the names. This is the #1 cause of "empty" fields in generated documents.

When a PDF takes time or you generate many at once, prefer QueueDocumentAsync: the work happens in the background through the message bus instead of blocking your request.


🚦 Before going to production

A checklist straight from the docs:

  • [ ] Database schema deployed with SQL scripts, Database.AutoCreate disabled
  • [ ] UseRabbitMq with a dedicated virtual host if the broker is shared
  • [ ] License and secrets (SMTP, Authentication:ClientSecret) in environment variables
  • [ ] PathRoot on a persistent volume (shared between instances); Redis configured if you run several instances
  • [ ] On Linux / Docker: Chromium installed (/usr/bin/chromium), required for PDF generation

That last point trips people up in containers: PDF rendering is browser-based (PuppeteerSharp), so a slim base image without Chromium will fail at generation time. A minimal Dockerfile addition on Debian-based images:

RUN apt-get update \
 && apt-get install -y --no-install-recommends chromium \
 && rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

🎯 Wrapping up

In a handful of lines you get a visual editor for your non-developer colleagues, a typed C# API for generating PDFs and emails, per-customer isolation if you need it, and no extra login to manage. If you want to see everything working end to end (login, PDF generation, background jobs, emails), the TemplateMaster team published a complete TemplateMasterDemo app on GitHub, linked from the docs.

Want to try it without any .NET setup first? Check out my previous post on running the whole TemplateMaster stack locally with Docker Compose.

👉 Full documentation: templatemaster.fr/fr/documentation/dotnet-integration

Are you generating PDFs in your .NET apps today? Which library are you using? Let me know in the comments! 👇

Top comments (0)