Building a scalable reporting backend with List & Label Cross Platform

The hard part isn’t generating reports. It’s building a reporting backend that scales cleanly. This post shows how to use List & Label Cross Platform to build a modern .NET architecture with stateless rendering, centralized template management, ASP.NET Core, Docker, and observability.

Estimated reading time: 8 minutes

scalable reporting backend with list & label

Key takeaways

  • Utilize List & Label Cross Platform (LLCP) for a stateless render engine to avoid Windows-only dependencies.
  • Implement repository mode to manage templates centrally and support multi-tenant architectures.
  • Deploy as a service using ASP.NET Core in Docker for horizontal scalability.
  • Incorporate observability to ensure reliability and performance under load.
  • Consider hybrid solutions for integrating web-based design tools.

Table of contents

Why LLCP for a reporting backend?

If you want a backend that runs on Linux, macOS, and Windows, in containers, and in CI/CD, you want List & Label Cross Platform rather than the classic Windows-only List & Label.

LLCP is a cross-platform reporting engine for .NET designed for server-side and cloud scenarios. It uses a unified API and JSON-based project files, targets background and backend usage, and is supported in environments like Docker (including Linux containers), Azure, ARM, and more. In other words, it’s built to sit behind an HTTP endpoint and churn out PDFs and images all day long.

Classic List & Label is still relevant when you need the Windows Designer or Web Report Designer backend, which currently require a Windows server and IIS.

But for a pure render service, LLCP is the component you actually want in your backend.

The working mental model:

LLCP is your render engine. It receives data + template ID + output format, generates the document, and hands it back. No UI, no state, no Windows dependency.


Core pattern: stateless render service

At the center of the architecture sits a stateless service that does one thing well: take a render request and return bytes.

This pattern is commonly used as part of a larger web application. In such cases, ensure that every developer working on the application has their own valid List & Label licence.

How a render request is processed:

  1. Accept a request with a template identifier (file path or repository:// ID), data selection parameters (for example, customerId or date range), and output format (PDF, PNG, JSON, …).
  2. Fetch data based on the parameters from your data source or API.
  3. Call LLCP to render the report.
  4. Stream the result back or write it to object storage and return a handle.

A minimal LLCP integration looks like this:

using combit.ListLabel31.CrossPlatform;
 
public class ReportService
{
    private readonly string _licenseKey = ""..."";
 
    public async Task<byte[]> RenderReportAsync(
        object dataSource,              // e.g., DataSet, IEnumerable<T>, DbDataReader
        string projectFileOrRepoId,     // path or repository://<id>
        string format                   // e.g. "PDF", "PNG"
    )
    {
        using var ll = new ListLabel
        {
            DataSource      = dataSource,
            AutoProjectFile = projectFileOrRepoId,
            LicensingInfo   = _licenseKey
        };
 
        using var ms = new MemoryStream();
 
        var exportConfig = new ExportConfiguration
        {
            ExportTarget = format,
            ExportStream = ms
        };
 
        ll.Export(exportConfig);
 
        return ms.ToArray();
    }
}

From a scalability perspective, two rules matter more than they might look at first glance.

One ListLabel instance per request: Keep instances short-lived and scoped to a single call. LLCP is designed for server/background use; sharing them across requests introduces statefulness and hard-to-debug behavior.

Avoid per-request local file access. Use repository IDs or templates stored in a database or object storage instead of reading from local disk for every render. Local disk ties you to specific machines and complicates horizontal scaling.

Think of the render service like a pure function: input (data + template ID + format) in, bytes out, no side effects.


Centralized template management with repository mode

Scaling reporting in multi-tenant or multi-instance setups is mostly a template distribution problem. Copying template files to every server or container does not scale and becomes a maintenance problem.

LLCP solves this via repository mode. Instead of file paths, you work with logical IDs such as repository://{GUID}. Behind that, you implement IRepository, which is responsible for loading and saving items (projects, images, etc.) from your chosen data store.

Conceptually, the repository becomes your single source of truth for templates. A straightforward implementation path:

  1. Implement IRepository on top of your storage of choice (for example, SQL database, S3, Azure Blob, file share).
  2. Store LLCP’s JSON-based project files and binary assets as repository items with metadata such as ID (GUID or string key), type (project, picture, etc.), content, timestamps for cache invalidation, and tenant information.
  3. Use repository:// IDs in your backend instead of physical file paths.

Using repository mode looks like this:

var reportId = "repository://a3f3e996-9b2d-4b2b-a1b8-123456789abc";
 
using var ll = new ListLabel
{
    DataSource      = dataSource,
    AutoProjectFile = reportId,
    LicensingInfo   = _licenseKey,
    Repository      = myRepositoryInstance
};
 
ll.Export(exportConfig);

This pattern unlocks several scaling benefits. Multiple containers can share the same templates via database or blob storage instead of shipping them in the image, rolling out template updates is a data change rather than a redeploy, and you can enforce tenant scoping and permissions in the repository implementation instead of scattering that logic across services.

If you are planning multi-tenant SaaS-style reporting, repository mode is effectively required.


Exposing the backend via ASP.NET Core

To make the render engine usable, wrap LLCP in a small HTTP API. ASP.NET Core with minimal APIs keeps the surface area small while fitting naturally into existing .NET backends.

A simplified example:

var builder = WebApplication.CreateBuilder(args);
 
builder.Services.AddSingleton<ReportService>();
 
var app = builder.Build();
 
app.MapPost("/render", async (RenderRequest req, ReportService svc) =>
{
    var data = await LoadDataAsync(req);  // your implementation
 
    var bytes = await svc.RenderReportAsync(
        data,
        req.ProjectId,                    // file path or repository://<id>
        req.Format                        // e.g. "PDF"
    );
 
    var contentType = req.Format switch
    {
        "PDF" => "application/pdf",
        "PNG" => "image/png",
        _     => "application/octet-stream"
    };
 
    return Results.File(
        bytes,
        contentType,
        $"report.{req.Format.ToLower()}"
    );
});
 
app.Run();
 
public record RenderRequest(
    string ProjectId,
    string Format,
    Dictionary<string, string> Parameters
);

This service intentionally stays small:

It is stateless: templates, input data, and outputs are all externalized to repositories, databases, or object storage.

It is easy to scale horizontally: behind a load balancer or API gateway because any instance can handle any request.


Containerization and cloud-native scaling

LLCP is built with containerized deployments in mind and supports modern .NET runtimes, so packaging the service for Docker and running it in Kubernetes, ECS, or Azure App Service is straightforward.

The main advantages of containerizing the reporting backend:

Dependency isolation: All native libraries for fonts and rendering live inside the image, which gives consistent behavior between development, staging, and production environments.

Cross-environment consistency: The same container image (typically Linux-based) can be used from development to production, regardless of whether developers work on Windows or macOS and deployment happens on Linux hosts.

Horizontal scalability: Scaling out is just adding replicas; LLCP instances remain stateless and self-contained.

A typical multi-instance architecture looks like this:

  • A cluster (Kubernetes, ECS, Azure App Service) running your containerized ASP.NET Core report service.
  • An external repository (SQL plus blob or object store) for templates and assets, exposed via IRepository.
  • Application or analytics databases for your business data.
  • Object storage (S3, Azure Blob, etc.) if you want to persist rendered outputs and hand back URLs instead of raw bytes.
  • An ingress controller or load balancer routing HTTP traffic to your report service pods.

Because each request constructs and disposes its own ListLabel instance and fetches everything from remote stores, you can usually scale linearly by adding replicas, constrained by CPU, memory, and capacity of external dependencies (database throughput, network I/O, storage performance).


Optional: Web Report Designer & Viewer (Windows-based)

So far, the focus has been on the backend render service. Many teams also need interactive template design in the browser or a rich web viewer. For that, List & Label provides two components in the Enterprise Edition:

  • Web Report Designer for browser-based template editing
  • Web Report Viewer for interactive report viewing in web apps

These components depend on the classic Windows-based List & Label stack. That means the backend needs to be an ASP.NET/MVC app on Windows, you add the combit.ListLabel31 and combit.ListLabel31.Web NuGet packages, and the server typically runs under IIS or a compatible environment.

This looks different from the cloud-native LLCP approach, but they work well together in a hybrid architecture.

A common pattern is to run a Windows-based design server that hosts the Web Report Designer and writes templates into your central repository, and to run a cross-platform LLCP render backend (the stateless service described earlier) in Linux containers for high-volume background and batch rendering. Both layers share templates via repository IDs, so the Windows design server and the LLCP render farm are loosely coupled.

Designers work in the browser, templates are saved to the repository, and your scalable backend picks them up on the next render call without needing to know anything about the design environment.


Performance and observability

Reporting often plays a key role in business-critical workflows such as billing, closing, or customer-facing documents. To keep things running smoothly, it’s worth adding basic observability from the start.

LLCP already provides a comprehensive logging and diagnostics infrastructure that surfaces detailed information about rendering, data access, and export processes. Your service should integrate these signals into your existing observability stack and complement them with application-level metrics.

There are three broad areas to cover:

Metrics: Track render duration, per-template latency, failure rate, queue length (if you place a queue in front of the service), CPU and memory usage per pod, and export size distributions. These metrics will guide your auto-scaling rules and help you spot regressions.

Logging: Capture LLCP logs and errors and enrich them with application context (template ID, tenant ID, parameter shape, but no sensitive payload data). Also log repository access failures and timeouts, and forward everything to your central logging stack.

Capacity planning: Use the metrics to define SLOs, set CPU and memory requests and limits for your containers, and estimate how many replicas you need for peak load.

In practice, this means instrumenting both your render service code and your IRepository implementation and wiring them into OpenTelemetry, Prometheus, or your preferred APM tool.


Reference architecture: Putting it together

To make the concepts concrete, here is a blueprint you can adapt:

1. Template management layer

  • A central repository backed by SQL plus blob or an object store, implementing IRepository for LLCP.
  • Optional Windows-based Web Report Designer backend for in-browser creation and editing of templates, writing directly into the repository.
  • Governance and metadata (versioning, tenant scoping, permissions) enforced at the repository layer.

2. Reporting backend layer (LLCP)

  • A stateless ASP.NET Core service using LLCP for rendering, packaged as a Docker image.
  • Deployed to Kubernetes, ECS, or Azure App Service on Linux or mixed OS, with horizontal scaling enabled.
  • Uses repository:// IDs and a shared IRepository implementation to resolve templates and assets.
  • Optional integration with queues (for example, SQS, Service Bus, RabbitMQ) for long-running or bulk reporting jobs.

3. API consumers

  • Line-of-business backends, web frontends, scheduled jobs, or serverless functions call the render API with template IDs and parameters.
  • Optional integration of the Web Report Viewer (Windows-based) inside existing .NET web apps for interactive previews, while bulk or scheduled exports go through the LLCP render service.

From the outside, other services only see a simple API:
“Render this template with these parameters in this format.”

Behind that, LLCP, repository mode, and your container platform handle scaling, template distribution, and cross-platform behavior.


Conclusion

A scalable reporting backend does not have to be a mix of scripts, shared network drives, and fragile Windows services. With List & Label Cross Platform you can treat reporting like any other backend concern: isolate it behind an HTTP API, run it in containers, store configuration and templates centrally, and scale it horizontally as demand grows.

The main ideas to keep in mind are:

  • Use LLCP as a stateless render engine instead of tying your backend to classic Windows-only components.
  • Adopt repository mode early to centralize templates, support multi-tenant scenarios, and decouple deployment from template rollout.
  • Wrap LLCP in a small ASP.NET Core service, keep it stateless, and run it in Docker for predictable, cross-platform behavior.
  • Add observability from the beginning so you can trust the system under load.
  • Layer on Web Report Designer / Viewer on a separate Windows-based backend if you need browser-based authoring and rich viewing.

With this architecture in place, generating PDFs for a handful of invoices or for millions of monthly statements becomes a question of scaling your containers, not rewriting your reporting stack.

FAQ

What is List & Label Cross Platform (LLCP)?

List & Label Cross Platform (LLCP) is a cross-platform reporting engine for .NET designed for server-side and cloud scenarios that allows for horizontal scalability and compatibility across different operating systems.

Why should I use repository mode?

Repository mode centralizes template management, supports multi-tenant scenarios, and decouples deployment from template rollout, making it easier to maintain and scale your reporting solutions.

Can LLCP be deployed in a containerized environment?

Yes, LLCP is built for containerized deployments and supports Docker, Kubernetes, ECS, and Azure App Service, ensuring consistent behavior across environments.

How does observability play a role in reporting systems?

Observability allows for tracking of performance metrics, logging errors, and planning for capacity, ensuring that the reporting system remains reliable and scalable under load.

What if I need a web-based design tool?

You can use the Web Report Designer provided in the Enterprise Edition, but it requires a Windows-based backend; this can be integrated with an LLCP render backend for high-volume rendering.

Related Posts