Skip to content

Advanced Data Sources

Peter Gill edited this page Jun 8, 2026 · 1 revision

Advanced Data Sources and Output Targets

This page covers patterns that go beyond the standard file-based workflow: reusing existing database connections, loading RDL from non-file sources, and streaming output to custom targets.

Loading RDL from non-file sources

RDLParser takes a plain string — the RDL XML. Anything that produces a string works.

From a database column

// Fetch the RDL XML from a database
await using var conn = new Microsoft.Data.SqlClient.SqlConnection(connectionString);
await conn.OpenAsync();
await using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT RdlXml FROM ReportTemplates WHERE Name = @name";
cmd.Parameters.AddWithValue("@name", "SalesReport");
var rdlXml = (string)await cmd.ExecuteScalarAsync();

// Parse and render as normal
var rdlp = new RDLParser(rdlXml) { Folder = @"C:\reports" };
using var report = await rdlp.Parse();

From an embedded resource

// report.rdl is an embedded resource in the assembly
var assembly = typeof(MyApp).Assembly;
using var stream = assembly.GetManifestResourceStream("MyApp.Reports.SalesReport.rdl")!;
using var reader = new StreamReader(stream);
var rdlXml = await reader.ReadToEndAsync();

var rdlp = new RDLParser(rdlXml);
using var report = await rdlp.Parse();

From a generated string

When using Programmatic Report Creation, Create.GenerateRdl returns a Report object directly — there is no intermediate file. For other programmatic cases:

string rdlXml = BuildReportDefinition();   // your RDL-generating method
var rdlp = new RDLParser(rdlXml);
using var report = await rdlp.Parse();

Reusing an existing database connection

By default the engine opens its own connection using the connection string embedded in the RDL. To reuse an existing IDbConnection — for transaction support, connection pooling, or multi-tenant isolation — assign it to DataSource.UserConnection before calling RunGetData:

using Majorsilence.Reporting.Rdl;

RdlEngineConfig.RdlEngineConfigInit();

var rdlp = new RDLParser(await File.ReadAllTextAsync("report.rdl"));
using var report = await rdlp.Parse();

// Supply your own open connection
using var conn = new Microsoft.Data.SqlClient.SqlConnection(tenantConnectionString);
await conn.OpenAsync();

report.DataSources["DataSourceName"].UserConnection = conn;

await report.RunGetData(null);

The DataSource name must match the one defined in the report. Use the designer's Data → Data Sources menu to find it.

Use cases:

  • Multi-tenant apps where the connection string is determined at request time
  • Sharing a connection or transaction across multiple reports
  • Avoiding repeated authentication round-trips when generating multiple reports in a batch

Custom output targets with IStreamGen

OneFileStreamGen and MemoryStreamGen both implement IStreamGen. You can implement the interface to stream rendered output to any target without touching the disk.

public interface IStreamGen
{
    Stream GetStream();
    TextWriter GetTextWriter();
    Stream GetIOStream(out string relativeName, string extension);
    void CloseMainStream();
}

Example — stream to Azure Blob Storage

public sealed class AzureBlobStreamGen : IStreamGen, IDisposable
{
    private readonly MemoryStream _buffer = new();
    private readonly BlobClient _blob;

    public AzureBlobStreamGen(BlobClient blob) => _blob = blob;

    public Stream GetStream()    => _buffer;
    public TextWriter GetTextWriter() => new StreamWriter(_buffer);
    public Stream GetIOStream(out string relativeName, string extension)
    {
        relativeName = $"resource{extension}";
        return _buffer;
    }
    public void CloseMainStream() { }

    public async Task CommitAsync()
    {
        _buffer.Position = 0;
        await _blob.UploadAsync(_buffer, overwrite: true);
    }

    public void Dispose() => _buffer.Dispose();
}

// Usage
var blobClient = containerClient.GetBlobClient("reports/sales-2024.pdf");
await using var sg = new AzureBlobStreamGen(blobClient);
await report.RunRender(sg, OutputPresentationType.PDF);
await sg.CommitAsync();

Example — stream directly to an ASP.NET Core HttpResponse

Response.ContentType = "application/pdf";
Response.Headers["Content-Disposition"] = "attachment; filename=report.pdf";

// Use MemoryStreamGen, then copy — do not write directly to Response.Body
// while the report is still rendering, as RunRender may seek the stream.
using var ms = new MemoryStreamGen();
await report.RunRender(ms, OutputPresentationType.PDF);

var buffer = ((MemoryStream)ms.GetStream()).ToArray();
await Response.Body.WriteAsync(buffer);

See Streaming PDF — ASP.NET Core for the full controller pattern.


Enumerating available data providers at runtime

string[] providers = RdlEngineConfig.GetProviders();
// e.g. ["Microsoft.Data.SqlClient", "PostgreSQL", "MySQL.NET", "Json", ...]

Useful for building dynamic connection dialogs or validating configuration at startup.


Registering a custom report item at runtime

Custom report items (barcodes, charts, QR codes) are normally declared in RdlEngineConfig.xml. You can also register them in code:

RdlEngineConfig.DeclareNewCustomReportItem("MyCustomChart", typeof(MyCustomChartRenderer));

MyCustomChartRenderer must implement ICustomReportItem. This is useful for plugins and assemblies loaded at runtime.

Clone this wiki locally