Let’s create a generic report web service in just 4 lines of AL code in Business Central

In this video, I create a Business Central web service that can run any report and deliver the finished report to the web service caller as a PDF file. And the central function is just 4 lines of AL code.

https://youtu.be/2eVNyGHQwAs

In this video, Erik demonstrates how to build a generic report web service for Business Central that can run any report with any parameters and return the result as a Base64-encoded string — all with just four essential lines of AL code. He then shows how to consume this web service from a .NET console application, passing report parameters as XML and saving the output as a PDF file.

The Problem: Exposing Reports as Web Services

A very common request Erik receives is: “How do I run a specific report — like an invoice or a statement — as a web service so I can retrieve it from an external system?” This is a common integration need: connecting Business Central’s reporting and document-generation capabilities into existing workflows and external systems.

Rather than creating individual web service endpoints for each report, Erik takes it up a notch and builds a generic report web service that can run any report by number, with any set of parameters.

The Four Essential Lines of AL Code

The core of the solution lives in a codeunit with a single procedure called RunReport. It accepts a report number and a parameters string (XML format), and returns the report output as a Base64-encoded string. Here are the four critical lines:

  1. Create an OutStream — Set up a memory buffer (via Temp Blob) with an OutStream to write to.
  2. Save the report — Use Report.SaveAs to render the report into that OutStream.
  3. Create an InStream — Create an InStream from the same Temp Blob to read the result back out.
  4. Convert to Base64 and return — Use the Base64 Convert codeunit to convert the binary stream to a Base64 string and return it.

Here is the complete codeunit with the PDF, Excel, and XML variants:

codeunit 50144 "Report Web Service"
{
    procedure RunReport(ReportNo: Integer; Parameters: Text): Text
    var
        OutS: OutStream;
        InS: InStream;
        TempBlob: Codeunit "Temp Blob";
        Base64: Codeunit "Base64 Convert";
    begin
        TempBlob.CreateOutStream(OutS);
        Report.SaveAs(ReportNo, Parameters, ReportFormat::Pdf, OutS);
        TempBlob.CreateInStream(InS);
        exit(Base64.ToBase64(InS));
    end;

    procedure RunReportAsExcel(ReportNo: Integer; Parameters: Text): Text
    var
        OutS: OutStream;
        InS: InStream;
        TempBlob: Codeunit "Temp Blob";
        Base64: Codeunit "Base64 Convert";
    begin
        TempBlob.CreateOutStream(OutS);
        Report.SaveAs(ReportNo, Parameters, ReportFormat::Excel, OutS);
        TempBlob.CreateInStream(InS);
        exit(Base64.ToBase64(InS));
    end;

    procedure RunReportAsXml(ReportNo: Integer; Parameters: Text): Text
    var
        OutS: OutStream;
        InS: InStream;
        TempBlob: Codeunit "Temp Blob";
        Base64: Codeunit "Base64 Convert";
    begin
        TempBlob.CreateOutStream(OutS);
        Report.SaveAs(ReportNo, Parameters, ReportFormat::Xml, OutS);
        TempBlob.CreateInStream(InS);
        exit(Base64.ToBase64(InS));
    end;
}

Understanding the Stream Pattern

The Temp Blob codeunit acts as an in-memory buffer. The OutStream allows Report.SaveAs to write the rendered report (a binary PDF, Excel file, or XML) into that buffer. Once the report is done, the InStream lets you read the content back out. Since you can’t return raw binary data through a SOAP web service text return value, the binary content is converted to a Base64 string, which the consumer can then decode back into bytes.

Exposing the Codeunit as a Web Service

To automatically publish the codeunit as a SOAP web service when the extension is installed, Erik includes a web service.xml file in the app package:

<?xml version="1.0" encoding="UTF-8"?>
<ExportedData>
    <TenantWebServiceCollection>
        <TenantWebService>
            <ObjectType>CodeUnit</ObjectType>
            <ServiceName>report</ServiceName>
            <ObjectID>50144</ObjectID>
            <Published>true</Published>
        </TenantWebService>
    </TenantWebServiceCollection>
</ExportedData>

This eliminates the need to manually go into Business Central and expose the codeunit as a web service — it’s all handled automatically when the extension is published.

Getting Report Parameters

The Report.SaveAs method accepts parameters as an XML string. But how do you know what that XML should look like? The key is the Report.RunRequestPage function, which opens the report’s request page and returns the user’s selections as an XML string.

Erik creates a simple helper page to retrieve these parameters:

page 50144 "Get Report Parms"
{
    PageType = Card;
    layout
    {
        area(Content)
        {
            field(ReportNo; ReportNo)
            {
                Caption = 'Report';
                ApplicationArea = all;
                trigger OnValidate()
                begin
                    message(Report.RunRequestPage(ReportNo));
                end;
            }
        }
    }
    var
        ReportNo: Integer;
}

When you enter a report number (e.g., 111 for “Customer – Top 10 List”), the request page opens. You configure your options (such as showing only the top 3 customers), click OK, and the page displays the resulting XML. You can then copy this XML and use it as the parameters value when calling the web service.

Consuming the Web Service from .NET

Erik demonstrates calling the web service from a .NET 5.0 console application using Windows Communication Foundation (WCF). Here’s a walkthrough of the client-side code:

Setting Up the WCF Connection

After adding a connected service reference to the SOAP endpoint in Visual Studio, the setup involves configuring the binding for basic HTTP authentication:

using System.IO;
using ReportService;

// Configure the binding
var binding = new System.ServiceModel.BasicHttpBinding();
binding.Security.Transport.ClientCredentialType = 
    System.ServiceModel.HttpClientCredentialType.Basic;
binding.MaxReceivedMessageSize = 999999999;
binding.Security.Mode = 
    System.ServiceModel.BasicHttpSecurityMode.TransportCredentialsOnly;

// Set up the endpoint
var endpoint = new System.ServiceModel.EndpointAddress(
    "http://localhost:7048/BC/WS/CRONUS/Codeunit/report");

// Create the client and supply credentials
var client = new Report_PortClient(binding, endpoint);
client.ClientCredentials.UserName.UserName = "demo";
client.ClientCredentials.UserName.Password = "demo";

Calling the Report and Saving the Result

With the client configured, calling the report and saving the result is straightforward:

// Call the web service (async in .NET Core WCF)
var result = await client.RunReportAsync(111, parametersXml);

// Convert from Base64 back to bytes and save as PDF
File.WriteAllBytes("report111.pdf", 
    Convert.FromBase64String(result.return_value));

The web service returns a Base64-encoded string. On the client side, Convert.FromBase64String decodes it back into a byte array, which is then written directly to a PDF file.

Passing Parameters

To pass report parameters, you supply the XML string obtained from Report.RunRequestPage. When embedding this XML in a C# string, remember to escape the double quotes (replacing " with \"). Erik demonstrates that passing parameters to show only the top 3 customers correctly changes the report output from the default top 5 to just 3.

Design Considerations

Erik offers some practical advice on extending this solution:

  • Multiple output formats: Rather than adding the report format as a parameter to a single method, Erik prefers creating separate endpoints like RunReport (PDF), RunReportAsExcel, and RunReportAsXml. Keeping web service signatures simple reduces integration issues for consumers.
  • Security: You may want to add additional authentication, authorization, or security checks depending on your environment.
  • Simplicity wins: The simpler the web service signature, the fewer issues subscribers will encounter. Erik recommends keeping parameters to basic types like integers and strings.

Summary

In under 30 minutes, Erik builds a complete generic report web service for Business Central. The AL-side solution is remarkably concise — four lines of code that create a buffer, render a report, read it back, and return it as Base64. Combined with the web service XML for auto-publishing and a helper page for extracting report parameters, this provides a clean, reusable pattern for integrating Business Central reports into any external system. On the consumer side, a simple .NET application demonstrates the full round-trip: calling the service, decoding the Base64 response, and saving the result as a PDF file.