Connect Firebase Realtime Database to Blazor Pivot Table

The Blazor Pivot Table can load and edit Firebase Realtime Database data through an ASP.NET Core API. SfDataManager sends HTTP requests to the API, and the API uses the Firebase Realtime Database REST API to read and persist JSON records stored under an Orders node.

This guide uses same-origin relative API URLs. The sample read action returns the complete Orders node and does not apply DataManagerRequest operations. Add server-side filtering, sorting, and paging before using this design with large datasets.

Prerequisites

The sample was tested with the following versions and configuration:

Software or package Version Notes
.NET SDK 10.0 Required to target net10.0. MapStaticAssets() requires .NET 9 or later.
Visual Studio 2026 (version 18.0) or later Required to target net10.0; install the ASP.NET and web development workload. VS Code and the .NET CLI are also supported
Firebase project Active project A Firebase project with Realtime Database enabled is required to host the data
Syncfusion.Blazor.PivotTable 34.1.33 (31.2.10 or later) .NET 10 support starts with 31.2.10; keep all Syncfusion packages on the same version
Syncfusion.Blazor.Themes 34.1.33 (31.2.10 or later) Provides the component theme

The application uses the Blazor Web App template with Interactive Server rendering. Syncfusion packages from NuGet.org require a valid license or trial key; follow the license-key registration instructions.

Firebase Realtime Database Setup and Application Configuration

Step 1: Create the Blazor Web App

Create an Interactive Server Blazor Web App:

dotnet new blazor -n PivotTableRealtimeDatabase -f net10.0 -int Server -ai
cd PivotTableRealtimeDatabase

In Visual Studio, the equivalent choices are Blazor Web App, .NET 10, Interactive render mode: Server, and Interactivity location: Global.

Step 2: Create the Firebase Project

Open the Firebase console and create a new project (for example, pivottablerealtimedatabase). Accept the default Google Analytics configuration or disable it; the Realtime Database does not require Analytics.

Step 3: Create the Realtime Database

In the Firebase console, navigate to Build → Realtime Database and click Create Database. Select Start in test mode so the REST API can read and write during development, and choose a region close to your users (for example, asia-southeast1).

Note: Test mode opens read and write access to the database for 30 days. Configure Firebase Security Rules before deploying the application. See Step 3.5 for a sample production ruleset.

After the database is provisioned, copy the database URL from the Data tab. This URL is required in appsettings.json later in this guide. The URL follows the format https://<project-id>-default-rtdb.<region>.firebasedatabase.app; projects created before February 2025 may use the legacy https://<project-id>.firebaseio.com host instead.

Step 4: Create the Orders Node and Import Sample Data

The Realtime Database stores data as a hierarchical JSON tree. Each order is stored as a child of a root node keyed by its orderId.

Create a root node named:

Orders

Use the Firebase console Import JSON panel to import the following sample data into the Orders node (the keys under Orders are numeric strings, e.g. "1", "2"):

{
  "Orders": {
    "1": {
      "orderId": 1,
      "customerName": "John Smith",
      "employeeId": 101,
      "freight": 32.5,
      "shipCity": "New York"
    },
    "2": {
      "orderId": 2,
      "customerName": "Toms",
      "employeeId": 102,
      "freight": 80.2,
      "shipCity": "London"
    },
    "3": {
      "orderId": 3,
      "customerName": "Sven",
      "employeeId": 101,
      "freight": 52.1,
      "shipCity": "Berlin"
    },
    "4": {
      "orderId": 4,
      "customerName": "Sara",
      "employeeId": 103,
      "freight": 18.4,
      "shipCity": "Madrid"
    },
    "5": {
      "orderId": 5,
      "customerName": "Paul",
      "employeeId": 102,
      "freight": 64.75,
      "shipCity": "Tokyo"
    }
  }
}

Verify the imported data in the Data tab:

Key orderId customerName employeeId freight shipCity
1 1 John Smith 101 32.50 New York
2 2 Toms 102 80.20 London
3 3 Sven 101 52.10 Berlin
4 4 Sara 103 18.40 Madrid
5 5 Paul 102 64.75 Tokyo

Note: The sample uses numeric keys (1–5) under the Orders node. The controller is implemented to deserialize the resulting REST API response shape; see the Read Operation in Step 7 for details.

The test-mode ruleset allows unrestricted read and write access for 30 days. Before you start building, replace the rules with a minimum locked-down ruleset that allows only the operations this sample needs. In the Firebase console, open Build → Realtime Database → Rules and replace the body with:

{
  "rules": {
    "Orders": {
      ".read": true,
      ".write": true,
      ".indexOn": ["orderId"]
    }
  }
}

Note: The .read: true / .write: true rules above still expose the database to anonymous clients and are only suitable for local development. For production, gate access behind an authentication provider (for example, Firebase Auth ID tokens) and restrict the rules to authenticated requests with the structure shown in the Firebase Security Rules guide. The .indexOn entry enables efficient WhereEqualTo queries against the orderId field once you switch to a query-based controller.

Step 5: Install the Required NuGet Packages

Run these commands in the PivotTableRealtimeDatabase project directory:

dotnet add package Syncfusion.Blazor.PivotTable --version 34.1.33
dotnet add package Syncfusion.Blazor.Themes --version 34.1.33
dotnet add package Newtonsoft.Json --version 13.0.4
dotnet restore

The project file should contain:

<ItemGroup>
  <PackageReference Include="Syncfusion.Blazor.PivotTable" Version="34.1.33" />
  <PackageReference Include="Syncfusion.Blazor.Themes" Version="34.1.33" />
  <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>

The sample accesses the Realtime Database directly through the REST API using HttpClient, so no Firebase client SDK package is required. The controller serializes and deserializes JSON with System.Text.Json; Newtonsoft.Json is referenced by the project for compatibility with the Syncfusion Blazor runtime dependencies but is not invoked directly by the controller code shown in this guide.

Note: If you are behind a corporate proxy or use an internal NuGet feed (for example, a Nexus or Artifactory mirror), pass --source https://api.nuget.org/v3/index.json to each dotnet add package command so the public Syncfusion packages are resolved from nuget.org. Confirm with dotnet nuget list source that the desired source appears first.

Step 6: Configure the Realtime Database URL

Store the Realtime Database URL in appsettings.json:

{
  "FirebaseSettings": {
    "DatabaseUrl": "<YOUR_FIREBASE_REALTIME_DATABASE_URL>"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

The required key is FirebaseSettings:DatabaseUrl. The other top-level keys (Logging, AllowedHosts) are the standard ASP.NET Core defaults. If appsettings.Development.json exists, it overrides these values when the app runs under the Development environment.

Note: Replace <YOUR_FIREBASE_REALTIME_DATABASE_URL> with the Realtime Database URL copied from the Firebase console Data tab (for example, https://<project-id>-default-rtdb.<region>.firebasedatabase.app). Older projects (created before February 2025) use the legacy host https://<project-id>.firebaseio.com; the controller works with either host. A relative URL is not supported here because the URL points to the Firebase host, not the application host.

Step 7: Create the API Controller

Create a Controllers folder at the project root and add OrderController.cs. The Order model and the CRUDModel<T> wrapper are declared as nested classes inside OrderController so the same types can be referenced from the controller and the Pivot Table page. The controller reads the Realtime Database URL from IConfiguration in its constructor and instantiates a single HttpClient field that is reused across requests.

Before showing the full file, here is a summary of what each endpoint does:

Endpoint HTTP verb Firebase REST call Action
Post POST /api/Order GET {databaseUrl}/Orders.json Returns the entire Orders node as { result, count }. The DataManagerRequest is intentionally ignored.
Insert POST /api/Order/Insert PUT {databaseUrl}/Orders/{nextOrderId}.json Computes the next orderId from the existing node, writes the new record, and returns it with the generated key.
Update POST /api/Order/Update PUT {databaseUrl}/Orders/{order.OrderID}.json Replaces the node at the supplied key path with the incoming record.
Delete POST /api/Order/Delete DELETE {databaseUrl}/Orders/{orderId}.json Removes the node at the supplied key path.

The endpoint behavior is also described in the API Contract section; the table above is a quick reference.

Controllers/OrderController.cs

using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using Syncfusion.Blazor;
using Syncfusion.Blazor.Data;

namespace PivotTableRealtimeDatabase.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class OrderController : ControllerBase
    {
        private readonly HttpClient httpClient;
        private readonly string databaseUrl;

        public OrderController(IConfiguration configuration)
        {
            databaseUrl =
                configuration["FirebaseSettings:DatabaseUrl"]
                ?? throw new InvalidOperationException(
                    "Firebase Realtime Database URL is not configured.");

            httpClient = new HttpClient();
        }

        [HttpPost]
        public async Task<object> Post(
            [FromBody] DataManagerRequest request)
        {
            _ = request;

            string url = $"{databaseUrl}/Orders.json";

            string json =
                await httpClient.GetStringAsync(url);

            List<Order?>? firebaseData =
                JsonSerializer.Deserialize<List<Order?>>(json);

            List<Order> orders =
                firebaseData?
                    .Where(x => x != null)
                    .Cast<Order>()
                    .OrderBy(x => x.OrderID)
                    .ToList()
                ?? new();

            return new
            {
                result = orders,
                count = orders.Count
            };
        }

        [HttpPost("Insert")]
        public async Task<IActionResult> Insert(
            [FromBody] CRUDModel<Order> value)
        {
            if (value.Value is not Order order)
            {
                return BadRequest();
            }

            string url = $"{databaseUrl}/Orders.json";

            string json =
                await httpClient.GetStringAsync(url);

            List<Order?>? firebaseData =
                JsonSerializer.Deserialize<List<Order?>>(json);

            int nextOrderId =
                firebaseData?
                    .Where(x => x != null)
                    .Max(x => x!.OrderID ?? 0) + 1
                ?? 1;

            order.OrderID = nextOrderId;

            string orderJson =
                JsonSerializer.Serialize(order);

            await httpClient.PutAsync(
                $"{databaseUrl}/Orders/{nextOrderId}.json",
                new StringContent(
                    orderJson,
                    System.Text.Encoding.UTF8,
                    "application/json"));

            return Ok(order);
        }

        [HttpPost("Update")]
        public async Task<IActionResult> Update(
            [FromBody] CRUDModel<Order> value)
        {
            if (value.Value is not Order order
                || !order.OrderID.HasValue)
            {
                return BadRequest();
            }

            string orderJson =
                JsonSerializer.Serialize(order);

            HttpResponseMessage response =
                await httpClient.PutAsync(
                    $"{databaseUrl}/Orders/{order.OrderID}.json",
                    new StringContent(
                        orderJson,
                        System.Text.Encoding.UTF8,
                        "application/json"));

            return response.IsSuccessStatusCode
                ? Ok(order)
                : BadRequest();
        }

        [HttpPost("Delete")]
        public async Task<IActionResult> Delete(
            [FromBody] CRUDModel<Order> value)
        {
            if (!int.TryParse(
                value.Key?.ToString(),
                out int orderId))
            {
                return BadRequest();
            }

            HttpResponseMessage response =
                await httpClient.DeleteAsync(
                    $"{databaseUrl}/Orders/{orderId}.json");

            return response.IsSuccessStatusCode
                ? NoContent()
                : NotFound();
        }

        public class Order
        {
            [Key]
            [JsonPropertyName("orderId")]
            public int? OrderID { get; set; }

            [JsonPropertyName("customerName")]
            public string? CustomerName { get; set; }

            [JsonPropertyName("employeeId")]
            public int? EmployeeID { get; set; }

            [JsonPropertyName("freight")]
            public double? Freight { get; set; }

            [JsonPropertyName("shipCity")]
            public string? ShipCity { get; set; }
        }

        public class CRUDModel<T> where T : class
        {
            [JsonPropertyName("action")]
            public string? Action { get; set; }

            [JsonPropertyName("keyColumn")]
            public string? KeyColumn { get; set; }

            [JsonPropertyName("key")]
            public object? Key { get; set; }

            [JsonPropertyName("value")]
            public T? Value { get; set; }

            [JsonPropertyName("added")]
            public List<T>? Added { get; set; }

            [JsonPropertyName("changed")]
            public List<T>? Changed { get; set; }

            [JsonPropertyName("deleted")]
            public List<T>? Deleted { get; set; }

            [JsonPropertyName("params")]
            public IDictionary<string, object>? Params { get; set; }
        }
    }
}

The controller exposes the read, insert, update, and delete endpoints described in the API Contract.

The Post action discards the incoming DataManagerRequest with _ = request; and returns the full Orders collection. The UrlAdaptor sends a DataManagerRequest object, but this sample intentionally returns the complete Orders collection; filtering, sorting, paging, and aggregation are performed after the data is loaded on the client. The REST response is deserialized into List<Order?> so any missing numeric positions in the Orders node are represented as null and filtered out with Cast<Order>().

The Insert action uses Max(x => x!.OrderID ?? 0) + 1 (falling back to 1 when the node is empty) so it is safe on an empty database — the first insert is assigned orderId = 1. The Insert and Update actions serialize the Order with System.Text.Json and issue a PutAsync to {databaseUrl}/Orders/{orderId}.json, replacing the node at that key path. The Delete action parses value.Key to an int, issues DeleteAsync to {databaseUrl}/Orders/{orderId}.json, and returns NoContent() on success or NotFound() when Firebase reports the key is absent.

The Insert action reads the entire Orders node on every insert to compute the next id. For large datasets, append ?shallow=true to the read URL and count the keys client-side to avoid a full payload transfer.

Order Model and CRUDModel

The Order model maps .NET properties to Realtime Database JSON fields through [JsonPropertyName], and OrderID carries [Key] so the Pivot Table can identify the primary key for update and delete requests. All value properties are declared nullable (int?, double?, string?) to mirror the optional-field shape of the Realtime Database JSON and to allow the Update action to validate order.OrderID.HasValue before issuing a PUT. Freight is declared as double? to match the Realtime Database JSON values. The Order and CRUDModel<T> types are declared as nested classes inside OrderController so they share one file and are visible to both the controller and the Pivot Table page.

The CRUDModel<T>.Action field is one of "insert", "update", or "remove" and identifies the operation; this sample routes writes by controller route (/Insert, /Update, /Delete) rather than reading Action, so the field is preserved for compatibility with the UrlAdaptor payload shape. The other properties (added, changed, deleted, params) are used for batch editing, which this sample does not enable.

Step 8: Configure Program.cs

Replace Program.cs with:

using PivotTableRealtimeDatabase.Components;
using Syncfusion.Blazor;

var builder = WebApplication.CreateBuilder(args);

// Register the Syncfusion license before builder.Build().
// Syncfusion packages require a valid license or trial key.
// Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(
//     "YOUR LICENSE KEY");

builder.Services.AddSyncfusionBlazor();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

app.UseExceptionHandler();

app.UseHttpsRedirection();

app.MapControllers();

app.UseAntiforgery();

app.MapStaticAssets();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

Key registration points:

  • AddSyncfusionBlazor() registers the Syncfusion Blazor services required by the Pivot Table.
  • AddRazorComponents().AddInteractiveServerComponents() enables Interactive Server rendering.
  • AddControllers() registers the API controllers, including OrderController.
  • AddProblemDetails() and UseExceptionHandler() log unhandled failures and return generic error responses.
  • MapControllers() routes requests to the API endpoints.
  • MapStaticAssets() is a .NET 9+ API; it maps the application’s static web assets, including assets supplied by referenced component packages.

The controller instantiates its own HttpClient field, so AddHttpClient() / IHttpClientFactory are not registered in this sample. If you move to a higher-throughput deployment, register IHttpClientFactory and update the controller to resolve clients from the factory instead (see Production Considerations).

Note: Uncomment the SyncfusionLicenseProvider.RegisterLicense(...) line and paste your Syncfusion license or trial key before running the application. Keep the key out of source control by loading it from appsettings.json, an environment variable, or a secrets manager. Follow the license-key registration instructions for details.

Note: app.UseHttpsRedirection() requires Properties/launchSettings.json to define an HTTPS applicationUrl (for example, "https://localhost:5001") and a trusted ASP.NET Core development certificate. On Windows, run dotnet dev-certs https --trust once after installing the SDK so the redirect does not fail with ERR_CERT_AUTHORITY_INVALID on first launch. The default dotnet new blazor launch profile already includes both, but confirm the file has not been edited away.

Note: app.UseAntiforgery() protects form posts and Razor component actions, but SfDataManager does not automatically attach an antiforgery token to its JSON POSTs. This sample leaves the controller without an antiforgery override and the UrlAdaptor requests are processed normally; if you observe 400 antiforgery failures when running the sample (depending on the .NET 10 template defaults), add an [IgnoreAntiforgeryToken] attribute on OrderController so the controller actions remain callable from the Pivot Table. If you later authenticate the API with cookies, configure the adaptor to send the antiforgery token and remove the ignore attribute.

Step 9: Configure the Pivot Table

Add these namespaces to Components/_Imports.razor:

@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.PivotView

Order is declared as a nested class inside OrderController and as a matching Order class in Home.razor, so no Models namespace import is required on the page.

In Components/App.razor, add the Syncfusion stylesheet inside <head>:

<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css"
      rel="stylesheet" />

Add the Syncfusion script immediately before </body>, after the template’s existing _framework/blazor.web.js reference:

<script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js"
        type="text/javascript"></script>

Do not add a second _framework/blazor.web.js reference if the template already contains one. The completed App.razor should look like:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
    <link rel="stylesheet" href="@Assets["app.css"]" />
    <link rel="stylesheet" href="@Assets["PivotTableRealtimeDatabase.styles.css"]" />
    <ImportMap />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet @rendermode="InteractiveServer" />
    <link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
</head>

<body>
    <Routes @rendermode="InteractiveServer" />
    <script src="_framework/blazor.web.js"></script>
    <script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js" type="text/javascript"></script>
</body>

</html>

Replace Components/Pages/Home.razor with the following markup. The SfDataManager URLs use same-origin relative paths (/api/Order, /api/Order/Insert, etc.) so they resolve against whatever port the application is launched on, avoiding hard-coded development ports and HTTP-to-HTTPS mixed-content failures:

@page "/"
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.PivotView

<SfPivotView TValue="Order" Width="1000" Height="300" ShowFieldList="true">
    <PivotViewDataSourceSettings TValue="Order" ExpandAll="false" EnableSorting="true">
        <SfDataManager Url="/api/Order"
                       InsertUrl="/api/Order/Insert"
                       UpdateUrl="/api/Order/Update"
                       RemoveUrl="/api/Order/Delete"
                       Adaptor="Adaptors.UrlAdaptor">
        </SfDataManager>
        <PivotViewColumns>
            <PivotViewColumn Name="EmployeeID"></PivotViewColumn>
        </PivotViewColumns>
        <PivotViewRows>
            <PivotViewRow Name="CustomerName"></PivotViewRow>
        </PivotViewRows>
        <PivotViewValues>
            <PivotViewValue Name="Freight" Caption="Freight"></PivotViewValue>
        </PivotViewValues>
    </PivotViewDataSourceSettings>
    <PivotViewGridSettings ColumnWidth="120"></PivotViewGridSettings>
    <PivotViewEvents TValue="Order" BeginDrillThrough="BeginDrillThrough"></PivotViewEvents>
    <PivotViewCellEditSettings AllowEditing="true"
                               AllowAdding="true"
                               AllowDeleting="true"
                               Mode="EditMode.Normal">
    </PivotViewCellEditSettings>
</SfPivotView>

@code {
    private void BeginDrillThrough(BeginDrillThroughEventArgs args)
    {
        // Identify the key used by URL Adaptor update and delete requests.
        for (int i = 0; i < args.GridObj.Columns.Count; i++)
        {
            if (args.GridObj.Columns[i].Field == "OrderID")
            {
                args.GridObj.Columns[i].IsPrimaryKey = true;
            }
            else
            {
                args.GridObj.Columns[i].Visible = true;
            }
        }
    }

    public class Order
    {
        public int? OrderID { get; set; }
        public string? CustomerName { get; set; }
        public int? EmployeeID { get; set; }
        public double? Freight { get; set; }
        public string? ShipCity { get; set; }
    }
}

The BeginDrillThrough event is used to mark OrderID as the primary key on the drill-through grid by setting args.GridObj.Columns[i].IsPrimaryKey = true; for the OrderID column. This tells the DataManager which column uniquely identifies each record so that insert, update, and delete requests carry the correct key. Without this configuration, the write operations cannot target the intended row. The Order class declared in the @code block mirrors the shape of the Order model on the server and is the type used by SfPivotView and the drill-through grid on the client.

API Contract

Method Route Payload Success response
POST /api/Order DataManagerRequest 200 with { result, count }
POST /api/Order/Insert CRUDModel<Order> 200 with the inserted record
POST /api/Order/Update CRUDModel<Order> 200 with the updated record
POST /api/Order/Delete CRUDModel<Order> 204 with no body

The API uses action-oriented routes because they match the UrlAdaptor’s InsertUrl, UpdateUrl, and RemoveUrl contract.

For write requests, action identifies the operation, keyColumn names the primary-key field, key carries the value used by delete operations, and value carries the inserted or updated record. The Syncfusion model also supports added, changed, and deleted collections for batch editing, params for additional values, and table for an optional table name; this sample uses normal editing and does not consume those optional properties.

Route Failure response
/api/Order 500 when the Realtime Database cannot be queried
/api/Order/Insert 400 when the request is missing the order value; 500 on a Realtime Database failure
/api/Order/Update 400 when required fields are missing or the OrderID is null; 500 on a Realtime Database failure
/api/Order/Delete 400 when the key is not numeric; 404 when the key does not exist; 500 on a Realtime Database failure

Example read response:

{
  "result": [
    {
      "orderId": 1,
      "customerName": "John Smith",
      "employeeId": 101,
      "freight": 32.5,
      "shipCity": "New York"
    },
    {
      "orderId": 2,
      "customerName": "Toms",
      "employeeId": 102,
      "freight": 80.2,
      "shipCity": "London"
    },
    {
      "orderId": 3,
      "customerName": "Sven",
      "employeeId": 101,
      "freight": 52.1,
      "shipCity": "Berlin"
    },
    {
      "orderId": 4,
      "customerName": "Sara",
      "employeeId": 103,
      "freight": 18.4,
      "shipCity": "Madrid"
    },
    {
      "orderId": 5,
      "customerName": "Paul",
      "employeeId": 102,
      "freight": 64.75,
      "shipCity": "Tokyo"
    }
  ],
  "count": 5
}

Example insert request:

{
  "action": "insert",
  "keyColumn": "orderId",
  "value": {
    "customerName": "Mei Chen",
    "employeeId": 104,
    "shipCity": "Sydney",
    "freight": 142.50
  }
}

The insert response returns the persisted record with the generated orderId populated.

Example update request:

{
  "action": "update",
  "keyColumn": "orderId",
  "value": {
    "orderId": 3,
    "customerName": "Sven",
    "employeeId": 101,
    "shipCity": "Hamburg",
    "freight": 60.00
  }
}

Example delete request:

{
  "action": "remove",
  "keyColumn": "orderId",
  "key": 5
}

Run and Verify the Application

Build and run the project:

dotnet build
dotnet run

Open the URL shown in the terminal. Verify the following:

  1. The Pivot Table displays CustomerName as rows, EmployeeID as columns, and the sum of Freight as values.
  2. The browser Network panel shows POST /api/Order returning 200 with result and count.
  3. Double-click a value (summary) cell to open its raw-record editor.
  4. Add a record and confirm that POST /api/Order/Insert returns the generated, nonzero orderId.
  5. Edit and delete records, and confirm the corresponding API requests succeed with that key.
  6. Open the Realtime Database Data tab in the Firebase console and confirm that the Orders node reflects the added, updated, and deleted records.

Production Considerations

Firebase Realtime Database is a JSON tree database suitable for real-time JSON storage and small-to-medium workloads. Before using this design in production, consider the following:

  • Security rules: Test mode opens the database for 30 days. Configure Firebase Security Rules that allow only the operations your application requires before deploying.
  • Authentication and authorization: This sample accesses the Realtime Database through anonymous REST requests that rely on test-mode rules. Production deployments should configure appropriate Firebase Realtime Database security rules, use a server-side authentication strategy appropriate for the application, protect the write endpoints with the application’s authorization mechanism, and avoid exposing unrestricted write access.
  • Server-side data operations: The sample returns the entire Orders node. Apply DataManagerRequest where, sorted, skip, and take parameters on the server before using this design with large datasets.
  • HTTP client lifetime: The sample instantiates a single HttpClient field per controller instance (effectively per request). For higher-throughput deployments, register IHttpClientFactory in Program.cs and resolve clients from the factory to avoid socket exhaustion and to centralize retry and timeout policies.
  • Database URL: Store the Realtime Database URL through the hosting environment or a secrets manager rather than committing production URLs to source control.
  • Antiforgery: If cookie-authenticated API actions require antiforgery validation, configure SfDataManager to send the request token expected by the server.
  • Region and latency: Select a Realtime Database region close to your application host to reduce REST API latency.
  • Deployment: For cross-origin hosting, configure an explicit CORS policy that allows only the Blazor application’s origin.

Troubleshooting

Symptom Resolution
Configuration and routing  
Firebase Realtime Database URL is not configured. Add a FirebaseSettings:DatabaseUrl entry to appsettings.json and restart the application.
405 Method Not Allowed Confirm [HttpPost], AddControllers(), and MapControllers() are present.
UrlAdaptor request fails Confirm Home.razor uses same-origin relative URLs (for example /api/Order) and that the Syncfusion stylesheet and script are registered in App.razor.
Database and network connectivity  
Unable to connect to Realtime Database Verify the DatabaseUrl value matches the URL shown in the Firebase console Data tab, including the region segment, and confirm the application host has outbound HTTPS access to *.firebasedatabase.app.
Realtime Database permission denied Verify the Firebase Realtime Database rules allow the requested operation, or test in development with test mode.
Browser reports mixed content or a redirect failure Confirm Home.razor uses same-origin relative URLs rather than hard-coded HTTP URLs, and that appsettings.json uses an HTTPS Realtime Database URL.
Cross-origin request is blocked Prefer same-origin relative URLs; otherwise configure AddCors and UseCors for the exact Blazor application origin.
Data and CRUD operations  
Pivot Table shows no data Inspect POST /api/Order in the browser Network panel and check the server log. Confirm the Orders node exists in the Realtime Database.
CRUD returns 400 Inspect the request JSON and confirm required fields are present, and that delete requests carry a numeric key.
CRUD returns 404 on delete Confirm that the supplied orderId exists in the Orders node and that OrderID is marked as the primary key in the BeginDrillThrough event handler.
CRUD returns 500 Check the server log and verify the Realtime Database URL and security rules.
JSON deserialization errors Confirm the Orders node uses sequential numeric keys. The controller deserializes the REST response into List<Order?>; non-sequential or string keys require a different deserialization strategy.
Auth, antiforgery, and performance  
Antiforgery validation fails This sample does not apply [IgnoreAntiforgeryToken]. If you observe 400 antiforgery failures under your template defaults, add the attribute to OrderController. For cookie-authenticated APIs, configure the adaptor to send the expected request token instead.
Large datasets are slow Process DataManagerRequest operations on the server instead of returning the entire Orders node.

For current component behavior, see the Pivot Table editing documentation and Pivot Table data-binding documentation.

Complete Sample Repository

A complete, working sample implementation is available in the GitHub repository.

Summary

This guide demonstrated how to bind a Firebase Realtime Database to the Syncfusion Blazor Pivot Table through an ASP.NET Core API and the UrlAdaptor. The Realtime Database REST API exposes the Orders node as JSON, and OrderController performs read and CRUD operations against that node using HttpClient. SfDataManager resolves the same-origin relative API URLs through the UrlAdaptor. The Pivot Table displays CustomerName as rows, EmployeeID as columns, and the sum of Freight as values, with drill-through editing that inserts, updates, and deletes records that are persisted back to the Realtime Database.