Skip to content

C# / ASP.NET

In this chapter, you’ll integrate Oicana into a C# web service using ASP.NET Core. ASP.NET Core is Microsoft’s modern, cross-platform framework for building web applications and APIs. We’ll create a simple web service that compiles your Oicana template to PDF and serves it via an HTTP endpoint.

Let’s start with a fresh ASP.NET project by executing dotnet new webapi in a new directory. The starter project has a single endpoint defined in Program.cs and exposes the OpenAPI document at /openapi/v1.json, but no interactive UI is bundled by default. To get an API explorer, add Scalar with dotnet add package Scalar.AspNetCore and wire it up in Program.cs next to the existing OpenAPI calls:

Part of Program.cs
using Scalar.AspNetCore; // <-- new
// ...
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference(); // <-- new
}

Start up the service (dotnet run) and open the URL printed in the terminal followed by /scalar. Expand the /weatherforecast endpoint, press “Test Request”, then “Send”. This will send an HTTP request to the running ASP.NET service and return made up weather data.

We will define a new endpoint to compile our Oicana template to a PDF and return the PDF file to the user.

  1. Create a new directory in the .NET project called templates and copy example-0.1.0.zip into that directory.

  2. Add the Oicana NuGet package as a dependency with dotnet add package Oicana.

  3. Read the template file and prepare it for compilation at the beginning of Program.cs:

    Part of Program.cs
    using Scalar.AspNetCore;
    using System.Text.Json.Nodes;
    using Oicana.Config;
    using Oicana.Inputs;
    using Oicana;
    var templateFile =
    await File.ReadAllBytesAsync("templates/example-0.1.0.zip");
    var template = new Template(templateFile);
  4. Replace the generated /weatherforecast endpoint with the following:

    Part of Program.cs
    app.MapPost("compile", () =>
    {
    var stream = template.Compile(
    new Dictionary<string, JsonNode>(),
    new Dictionary<string, BlobInput>(),
    ExportFormat.Pdf(),
    new CompilationOptions(CompilationMode.Development));
    var now = DateTimeOffset.Now;
    return Results.File(
    fileStream: stream,
    contentType: "application/pdf",
    fileDownloadName: $"example_{now:yyyy_MM_dd_HH_mm_ss_ffff}.pdf"
    );
    });

    This code defines a new POST endpoint at /compile. For every request, it compiles the template to PDF with two empty input dictionaries and returns the file. We use CompilationMode.Development here to demonstrate how the template falls back to the development value you defined for the info input ({ "name": "Chuck Norris" }). In a later step we will explicitly set a value for the input.

After restarting the service and refreshing the Scalar UI, you should see the new endpoint. Click “Test Request” and “Send” to get a preview of the returned PDF file.

The 200 response from calling the compile endpoint in the Scalar API explorer. The returned PDF is rendered and can be downloaded.

The PDF generation should not take longer than a couple of milliseconds. You can look at the request duration in the network tab of your browser’s debugging tools for an estimation. The first request to an ASP.NET service can be significantly slower than later ones, because ASP.NET does some preparation during the first request.

For a better measurement of the compilation speed on your machine, you can use a Stopwatch in the endpoint code.

Our compile endpoint is currently calling the template’s Compile method with empty dictionaries. This compiles the template without any explicit inputs. The first dictionary could contain JSON inputs (key to JsonNode) and the second blob inputs (key to BlobInput). Now we’ll provide an input value and switch to production mode.

Change the endpoint to set the name input you defined earlier.

Part of Program.cs
app.MapPost("compile", () =>
{
var jsonInputs = new Dictionary<string, JsonNode>
{
["info"] = JsonNode.Parse("{ \"name\": \"Baby Yoda\" }")!
};
var stream = template.Compile(
jsonInputs,
new Dictionary<string, BlobInput>(),
ExportFormat.Pdf(),
new CompilationOptions(CompilationMode.Production));
var now = DateTimeOffset.Now;
// ... more code from before
});

Notice that we switched to CompilationMode.Production now that we’re providing explicit input values. Production mode is the recommended default for all document compilation in your application - it ensures you never accidentally generate a document with test data. In production mode, the template will never fall back to development values. If an input value is missing in production mode and the input does not have a default value, the compilation will fail unless your template handles none values for that input.

Calling the endpoint now, will result in a PDF with “Baby Yoda” instead of “Chuck Norris”. Building on this minimal service, one could set input values based on database entries or the request payload. Take a look at the open source ASP.NET example project on GitHub for a more complete showcase of the Oicana C# integration.

Complete code at the end of this chapter
Program.cs
using Scalar.AspNetCore;
using System.Text.Json.Nodes;
using Oicana.Config;
using Oicana.Inputs;
using Oicana;
var templateFile =
await File.ReadAllBytesAsync("templates/example-0.1.0.zip");
var template = new Template(templateFile);
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.MapPost("compile", () =>
{
var jsonInputs = new Dictionary<string, JsonNode>
{
["info"] = JsonNode.Parse("{ \"name\": \"Baby Yoda\" }")!
};
var stream = template.Compile(
jsonInputs,
new Dictionary<string, BlobInput>(),
ExportFormat.Pdf(),
new CompilationOptions(CompilationMode.Production));
var now = DateTimeOffset.Now;
return Results.File(
fileStream: stream,
contentType: "application/pdf",
fileDownloadName: $"example_{now:yyyy_MM_dd_HH_mm_ss_ffff}.pdf"
);
});
app.Run();