Connect MongoDB to a Blazor Pivot Table Using the URL Adaptor
6 Aug 202624 minutes to read
The Blazor Pivot Table can load and edit MongoDB data through an ASP.NET Core API. SfDataManager sends HTTP requests to the API, and the API uses MongoDB.Driver to access the MongoDB database.
This guide uses same-origin relative API URLs. The sample read action returns the complete Orders collection 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
|
| Visual Studio | 2026 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 |
| MongoDB Community Server | 8.0 or later | The MongoDB daemon (mongod) supplies the document database runtime; install it as a service or run it manually |
| MongoDB Compass | Latest | The official MongoDB GUI used to create, inspect, and import data into collections |
| Syncfusion.Blazor.PivotTable | 34.1.33 | .NET 10 support starts with 31.2.10; keep all Syncfusion packages on the same version |
| Syncfusion.Blazor.Themes | 34.1.33 | Provides the component theme |
| MongoDB.Driver | 3.10.0 | Official MongoDB C# driver used by the API to read and write documents |
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.
MongoDB Architecture
The data flows through the following layers:
Blazor Pivot Table
↓
SfDataManager
↓
UrlAdaptor
↓
OrderController
↓
MongoDB.Driver
↓
MongoDB → OrderDB (database) → Orders (collection)The Blazor Pivot Table renders aggregated data and issues read and write requests through SfDataManager. The UrlAdaptor serializes those requests as HTTP POST calls to the OrderController API. The controller uses MongoDB.Driver to run find, insert, update, and delete operations against the OrderDB database and its Orders collection, and returns JSON responses that the adaptor understands.
MongoDB Database Setup
Step 1: Install MongoDB Community Server
Download MongoDB Community Server from the MongoDB download page and install it for your platform. On Windows, accept the option to run MongoDB as a service so that the daemon (mongod) starts automatically and listens on the default port 27017.
Verify the server is running. If the MongoDB installer did not add the bin folder to your PATH (it usually does not on Windows), run the command from the installation directory, for example C:\Program Files\MongoDB\Server\8.0\bin:
mongod --versionTip: To run
mongodfrom any directory, addC:\Program Files\MongoDB\Server\8.0\binto your systemPATHand restart the terminal.
Confirm the service is listening:
Get-Service MongoDBStep 2: Install MongoDB Compass
Download MongoDB Compass from the MongoDB Compass download page and install it. Compass is the official MongoDB GUI used to browse databases, inspect collections, and import documents.
Step 3: Connect to MongoDB
Open MongoDB Compass and connect to the local server using the default connection string:
mongodb://localhost:27017Note: If you enabled authentication during installation, use a connection string that includes the database, username, and password, for example
mongodb://username:password@localhost:27017/?authSource=admin.
Step 4: Create the Database and Collection
In MongoDB Compass:
- Click Create Database.
- Enter the database name
OrderDB. - Enter the collection name
Orders. - Click Create Database.
You can also create the database and collection from the mongosh shell:
use OrderDB
db.createCollection("Orders")Step 5: Import Sample Order Documents
Each document in the Orders collection represents a single order. Import the following sample documents into the Orders collection through MongoDB Compass (Add Data > Import JSON File) or mongosh:
db.Orders.insertMany([
{ orderId: 1, customerName: "Toms", employeeId: 1, freight: 35.30, shipCity: "New York" },
{ orderId: 2, customerName: "Ravi", employeeId: 2, freight: 80.20, shipCity: "London" },
{ orderId: 3, customerName: "Sven", employeeId: 1, freight: 52.10, shipCity: "Berlin" },
{ orderId: 4, customerName: "Sara", employeeId: 3, freight: 18.40, shipCity: "Madrid" },
{ orderId: 5, customerName: "Paul", employeeId: 2, freight: 64.75, shipCity: "Tokyo" }
])Step 6: Verify the Data
In MongoDB Compass, select the OrderDB database and the Orders collection. The documents should appear as:
| _id (ObjectId) | orderId | customerName | employeeId | freight | shipCity |
|---|---|---|---|---|---|
| (auto) | 1 | Toms | 1 | 35.30 | New York |
| (auto) | 2 | Ravi | 2 | 80.20 | London |
| (auto) | 3 | Sven | 1 | 52.10 | Berlin |
| (auto) | 4 | Sara | 3 | 18.40 | Madrid |
| (auto) | 5 | Paul | 2 | 64.75 | Tokyo |
Note: MongoDB automatically generates a unique
_id(ObjectId) for each document when one is not supplied. The application maps_idto theIdproperty of theOrdermodel, and uses the business keyorderIdfor update and delete operations.
Create the Blazor Web App
Create an Interactive Server Blazor Web App.
dotnet new blazor -n PivotTableMongoDB -f net10.0 -i Server -ai
cd PivotTableMongoDBIn Visual Studio, the equivalent choices are Blazor Web App, .NET 10, Interactive render mode: Server, and Interactivity location: Global.
Install the Required NuGet Packages
Run these commands in the PivotTableMongoDB 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 MongoDB.Driver --version 3.10.0Note: The Syncfusion Blazor packages 34.1.33 are published to the public NuGet.org feed. If your environment only resolves a private NuGet source, add the Syncfusion feed or set the
nuget.orgsource (https://api.nuget.org/v3/index.json) as a package source before running the commands above. Restore packages after adding them so the project references resolve:
dotnet restoreThe 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="MongoDB.Driver" Version="3.10.0" />
</ItemGroup>Configure the Connection String
Store the MongoDB connection string and the database and collection names in appsettings.json:
{
"ConnectionStrings": {
"MongoDB": "mongodb://localhost:27017"
},
"MongoDbSettings": {
"DatabaseName": "OrderDB",
"CollectionName": "Orders"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}Note: Use
mongodb://localhost:27017for a local MongoDB server without authentication. For a remote or secured MongoDB instance, replace the connection string with the appropriate URI, for examplemongodb+srv://<user>:<password>@cluster0.example.net/?retryWrites=true&w=majority. Keep production connection strings out of source control through user secrets or environment variables.
To override the connection string for local development without committing it to source, add an appsettings.Development.json file (already git-ignored by the Blazor template) or use the user-secrets store:
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:MongoDB" "mongodb://localhost:27017"appsettings.Development.json:
{
"ConnectionStrings": {
"MongoDB": "mongodb://localhost:27017"
}
}A MongoDbSettings:DatabaseName value of OrderDB and a MongoDbSettings:CollectionName value of Orders must match the database and collection created in Step 4.
Create the API Controller
Create a Controllers folder at the project root, and then create Controllers/OrderController.cs. In this sample, the Order model and the CRUDModel<T> wrapper are defined inside OrderController.cs rather than in a separate file. The same Order shape is also declared in the Pivot Table page (Home.razor) so the component can strongly type its data source. Defining the model alongside the code that consumes it keeps the contract between the controller and the page clear.
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using Syncfusion.Blazor;
using Syncfusion.Blazor.Data;
namespace PivotTableMongoDB.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
private readonly IMongoCollection<Order> ordersCollection;
public OrderController(IConfiguration configuration)
{
string connectionString =
configuration.GetConnectionString("MongoDB")
?? throw new InvalidOperationException(
"The MongoDB connection string is not configured.");
string databaseName =
configuration["MongoDbSettings:DatabaseName"]
?? "OrderDB";
string collectionName =
configuration["MongoDbSettings:CollectionName"]
?? "Orders";
MongoClient client = new(connectionString);
IMongoDatabase database = client.GetDatabase(databaseName);
ordersCollection =
database.GetCollection<Order>(collectionName);
}
[HttpPost]
public object Post([FromBody] DataManagerRequest request)
{
_ = request;
List<Order> dataSource = ordersCollection
.Find(_ => true)
.SortBy(x => x.OrderID)
.ToList();
return new
{
result = dataSource,
count = dataSource.Count
};
}
[HttpPost("Insert")]
public IActionResult Insert([FromBody] CRUDModel<Order> value)
{
if (value.Value is not Order order
|| string.IsNullOrWhiteSpace(order.CustomerName)
|| !order.EmployeeID.HasValue)
{
return BadRequest(
"CustomerName and EmployeeID are required.");
}
Order? lastRecord = ordersCollection
.Find(_ => true)
.SortByDescending(x => x.OrderID)
.FirstOrDefault();
order.OrderID = lastRecord?.OrderID + 1 ?? 1;
ordersCollection.InsertOne(order);
return Ok(order);
}
[HttpPost("Update")]
public IActionResult Update([FromBody] CRUDModel<Order> value)
{
if (value.Value is not Order order
|| !order.OrderID.HasValue
|| string.IsNullOrWhiteSpace(order.CustomerName)
|| !order.EmployeeID.HasValue)
{
return BadRequest(
"OrderID, CustomerName and EmployeeID are required.");
}
UpdateDefinition<Order> update =
Builders<Order>.Update
.Set(x => x.CustomerName, order.CustomerName)
.Set(x => x.EmployeeID, order.EmployeeID)
.Set(x => x.Freight, order.Freight)
.Set(x => x.ShipCity, order.ShipCity);
UpdateResult result =
ordersCollection.UpdateOne(
x => x.OrderID == order.OrderID,
update);
return result.MatchedCount == 0
? NotFound()
: Ok(order);
}
[HttpPost("Delete")]
public IActionResult Delete([FromBody] CRUDModel<Order> value)
{
if (!int.TryParse(
value.Key?.ToString(),
out int orderId))
{
return ValidationProblem(
"A numeric order key is required.");
}
DeleteResult result =
ordersCollection.DeleteOne(
x => x.OrderID == orderId);
return result.DeletedCount == 0
? NotFound()
: NoContent();
}
public class Order
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }
[BsonElement("orderId")]
public int? OrderID { get; set; }
[BsonElement("customerName")]
public string? CustomerName { get; set; }
[BsonElement("employeeId")]
public int? EmployeeID { get; set; }
[BsonElement("freight")]
public double? Freight { get; set; }
[BsonElement("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 OrderController constructor resolves the MongoDB connection string, database name, and collection name from configuration, creates a MongoClient, and caches the IMongoCollection<Order> used by every action. The exception middleware configured in Configure Program.cs returns generic problem responses without exposing connection strings or driver details.
Configure Program.cs
Replace Program.cs with:
using PivotTableMongoDB.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.UseExceptionHandler();
app.UseHsts();
}
else
{
// The developer exception page shows stack traces during local development.
app.UseDeveloperExceptionPage();
}
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapControllers();
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, includingOrderController. -
AddProblemDetails()andUseExceptionHandler()log unhandled failures and return genericProblemDetailserror responses. -
UseAntiforgery()is registered beforeMapControllers()so the antiforgery middleware is in the pipeline for any endpoint that opts into validation. The sample API actions do not require it, but ordering it correctly now avoids surprises if you later add cookie authentication. -
MapControllers()routes requests to the API endpoints. -
MapStaticAssets()maps the application’s static web assets, including assets supplied by referenced component packages.
Note: Remove the comment markers and fill in your Syncfusion license or trial key in
Program.csbefore running the application. Follow the license-key registration instructions for details.
Configure the Pivot Table
Add these namespaces to Components/_Imports.razor:
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.PivotViewIn 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["PivotTableMongoDB.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. 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 document.
Note: The
Orderclass declared inHome.razormirrors the shape sent by the API, but it does not include the MongoDB_idfield. The Pivot Table only needs the business fields for aggregation and editing; MongoDB’s internal_idis preserved on the server and never modified by the controller.
MongoDB Document Structure
Each row in the Pivot Table corresponds to one document in the Orders collection. A sample document (matching the records imported in Step 5) looks like:
{
"_id": "65a1f2c3b7d8e9f0a1b2c3d4",
"orderId": 1,
"customerName": "Toms",
"employeeId": 1,
"freight": 35.30,
"shipCity": "New York"
}| Field | Type | Description |
|---|---|---|
_id |
ObjectId | MongoDB-generated unique identifier for each document. Mapped to Order.Id with [BsonId] and [BsonRepresentation(BsonType.ObjectId)]; never edited by the Pivot Table. |
orderId |
Integer | Business key used by the read, insert, update, and delete operations. Auto-incremented on insert from the highest existing orderId; mapped to Order.OrderID with [BsonElement("orderId")]. |
customerName |
String | Name of the customer; required for insert and update; mapped to Order.CustomerName with [BsonElement("customerName")]. |
employeeId |
Integer | Identifier of the employee who placed the order; required for insert and update; mapped to Order.EmployeeID with [BsonElement("employeeId")]. |
freight |
Double | Shipping cost aggregated by the Pivot Table; mapped to Order.Freight with [BsonElement("freight")]. |
shipCity |
String | Destination city; mapped to Order.ShipCity with [BsonElement("shipCity")]. |
Note: The
[BsonElement]attributes control the field names written to MongoDB. The[BsonId]and[BsonRepresentation]attributes tell the driver thatIdis the document primary key and should be stored as anObjectId. Mismatched element names are the most common cause of empty Pivot Tables, so keep the attribute names in sync with the fields imported in Step 5.
CRUD Operations
The action methods below are defined in the OrderController class shown in Create the API Controller. Each subsection reproduces a single method and explains how it implements the corresponding read, insert, update, or delete operation.
Read
The read operation returns every document in the Orders collection to the Pivot Table:
[HttpPost]
public object Post([FromBody] DataManagerRequest request)
{
_ = request;
List<Order> dataSource = ordersCollection
.Find(_ => true)
.SortBy(x => x.OrderID)
.ToList();
return new
{
result = dataSource,
count = dataSource.Count
};
}How it works:
-
ordersCollectionresolves to theOrderDB.Orderscollection configured in the constructor. -
Find(_ => true)matches all documents in the collection. -
SortBy(x => x.OrderID)orders the results byorderIdso rows appear in a stable order in the drill-through grid. - The action wraps the documents in a
{ result, count }envelope that the UrlAdaptor expects.
Insert
The insert operation creates a new document and assigns the next available orderId:
[HttpPost("Insert")]
public IActionResult Insert([FromBody] CRUDModel<Order> value)
{
if (value.Value is not Order order
|| string.IsNullOrWhiteSpace(order.CustomerName)
|| !order.EmployeeID.HasValue)
{
return ValidationProblem(
"CustomerName and EmployeeID are required.");
}
Order? lastRecord = ordersCollection
.Find(_ => true)
.SortByDescending(x => x.OrderID)
.FirstOrDefault();
order.OrderID = lastRecord?.OrderID + 1 ?? 1;
ordersCollection.InsertOne(order);
return Ok(order);
}How it works:
- The action validates that
CustomerNameandEmployeeIDare present before writing. - It locates the document with the highest
orderIdand computes the next value aslastRecord.OrderID + 1, or1when the collection is empty. MongoDB does not provide an auto-incrementing integer field, so the controller generates the business key. -
InsertOnewrites the new document. MongoDB assigns the_idautomatically because none is supplied. - The inserted
Order(with the generatedOrderID) is returned so the Pivot Table can update its local copy.
Update
The update operation modifies an existing document by orderId while preserving the MongoDB _id:
[HttpPost("Update")]
public IActionResult Update([FromBody] CRUDModel<Order> value)
{
if (value.Value is not Order order
|| !order.OrderID.HasValue
|| string.IsNullOrWhiteSpace(order.CustomerName)
|| !order.EmployeeID.HasValue)
{
return ValidationProblem(
"OrderID, CustomerName and EmployeeID are required.");
}
UpdateDefinition<Order> update =
Builders<Order>.Update
.Set(x => x.CustomerName, order.CustomerName)
.Set(x => x.EmployeeID, order.EmployeeID)
.Set(x => x.Freight, order.Freight)
.Set(x => x.ShipCity, order.ShipCity);
UpdateResult result =
ordersCollection.UpdateOne(
x => x.OrderID == order.OrderID,
update);
return result.MatchedCount == 0
? NotFound()
: Ok(order);
}How it works:
- The filter
x => x.OrderID == order.OrderIDlocates the document by business key. UsingUpdateOneinstead of replacing the document keeps the original_idand any other unedited fields intact. -
Builders<Order>.Update.Setbuilds an update definition that changes only the editable fields. - When no document matches the supplied
orderId, the action returns404so the Pivot Table can surface the failure.
Delete
The delete operation removes a document by orderId:
[HttpPost("Delete")]
public IActionResult Delete([FromBody] CRUDModel<Order> value)
{
if (!int.TryParse(
value.Key?.ToString(),
out int orderId))
{
return ValidationProblem(
"A numeric order key is required.");
}
DeleteResult result =
ordersCollection.DeleteOne(
x => x.OrderID == orderId);
return result.DeletedCount == 0
? NotFound()
: NoContent();
}How it works:
- The UrlAdaptor sends the primary key in the
keyproperty. The action parses it as an integer before querying. -
DeleteOneremoves the matching document. The action returns204on success and404when no document matches the key.
API Contract
| Method | Route | Payload | Success response | Failure response |
|---|---|---|---|---|
POST |
/api/Order |
DataManagerRequest |
200 with { result, count }
|
500 when the database cannot be queried |
POST |
/api/Order/Insert |
CRUDModel<Order> |
200 with the inserted record |
400 when required fields are missing; 500 on a database failure |
POST |
/api/Order/Update |
CRUDModel<Order> |
200 with the updated record |
400 when required fields are missing; 404 when the key does not exist; 500 on a database failure |
POST |
/api/Order/Delete |
CRUDModel<Order> |
204 with no body |
400 when the key is not numeric; 404 when the key does not exist; 500 on a database failure |
The API uses action-oriented routes because they match the URL Adaptor’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 local CRUDModel<T> class also exposes added, changed, and deleted collections (used by the URL Adaptor in batch edit mode) and params for additional values. This sample uses normal editing and does not consume those optional properties.
Example read response:
{
"result": [
{
"id": "65a1f2c3b7d8e9f0a1b2c3d4",
"orderID": 1,
"customerName": "Toms",
"employeeID": 1,
"freight": 35.30,
"shipCity": "New York"
},
{
"id": "65a1f2c3b7d8e9f0a1b2c3d5",
"orderID": 2,
"customerName": "Ravi",
"employeeID": 2,
"freight": 80.20,
"shipCity": "London"
},
{
"id": "65a1f2c3b7d8e9f0a1b2c3d6",
"orderID": 3,
"customerName": "Sven",
"employeeID": 1,
"freight": 52.10,
"shipCity": "Berlin"
},
{
"id": "65a1f2c3b7d8e9f0a1b2c3d7",
"orderID": 4,
"customerName": "Sara",
"employeeID": 3,
"freight": 18.40,
"shipCity": "Madrid"
},
{
"id": "65a1f2c3b7d8e9f0a1b2c3d8",
"orderID": 5,
"customerName": "Paul",
"employeeID": 2,
"freight": 64.75,
"shipCity": "Tokyo"
}
],
"count": 5
}Note: The JSON payloads below use camelCase property names (
orderID,customerName,employeeID,freight,shipCity) because the URL Adaptor serializes fields from the grid’s camelCase wire format and the API deserializes them into the PascalCase C# properties ofOrder(OrderID,CustomerName,EmployeeID,Freight,ShipCity) through System.Text.Json’s default case-insensitive binding. ThekeyColumnvalue (orderID) must match the field name the grid sends — the one you marked asIsPrimaryKeyin theBeginDrillThroughhandler — and not the C# property name.
Example insert request:
{
"action": "insert",
"keyColumn": "orderID",
"value": {
"customerName": "Mei Chen",
"employeeID": 4,
"shipCity": "Sydney",
"freight": 142.50
}
}The insert response returns the persisted record with the generated orderID and the MongoDB-assigned id populated.
Example update request:
{
"action": "update",
"keyColumn": "orderID",
"value": {
"orderID": 3,
"customerName": "Sven",
"employeeID": 1,
"shipCity": "Hamburg",
"freight": 60.00
}
}Example delete request:
{
"action": "remove",
"keyColumn": "orderID",
"key": 5
}Run and Verify the Application
After editing Program.cs, Home.razor, and App.razor, restore dependencies and build the project. Ensure the Syncfusion license or trial key has been registered in Program.cs (see Configure Program.cs) before building; an unregistered key does not fail the build, but the component will render a license warning at runtime.
dotnet restore
dotnet build
dotnet runBefore launching the app, confirm the MongoDB service is reachable. The OrderController constructor opens the collection eagerly and will throw on the first request if MongoDB is not listening on 27017:
Get-Service MongoDB # should report Status: RunningOpen the URL shown in the terminal. Verify the following:
- The Pivot Table displays
CustomerNameas rows,EmployeeIDas columns, and the sum ofFreightas values. - The browser Network panel shows
POST /api/Orderreturning200withresultandcount. - Double-click a value (summary) cell to open its raw-record editor.
- Add a record and confirm that
POST /api/Order/Insertreturns the generated, nonzeroorderID. - Edit and delete records, and confirm the corresponding API requests succeed with that key.
- Inspect the
Orderscollection in MongoDB Compass to confirm the changes were persisted. For deployed apps with no direct database access, re-open the drill-through editor and confirm that the rows reflect the changes.
Production Considerations
MongoDB is ideal for document-oriented workloads and scales horizontally through sharding. Before using this design in production, consider the following:
-
Concurrency: MongoDB supports concurrent readers and writers. For write-heavy workloads, prefer
UpdateOneoverReplaceOne(whole-document replacement), and index theorderIdfield used by update and delete filters. -
Server-side data operations: The sample returns the entire
Orderscollection. ApplyDataManagerRequestwhere,sorted,skip, andtakeparameters on the server before using this design with large datasets. - Connection string: Store the MongoDB connection string through the hosting environment or a secrets manager rather than committing production URIs to source control.
- Authentication and authorization: Configure MongoDB authentication and protect the write endpoints with the authentication and authorization mechanism used by your application.
-
Antiforgery: If cookie-authenticated API actions require antiforgery validation, configure
SfDataManagerto send the request token expected by the server. Add a custom header name inProgram.csand read it inside the action filter:
builder.Services.AddAntiforgery(o => o.HeaderName = "X-CSRF-TOKEN");Then configure the data manager to send the same header by adding a custom adaptor option (the default UrlAdaptor does not forward antiforgery tokens; a small custom adaptor is the cleanest way). For non-cookie authentication (bearer tokens, API keys) skip this step entirely.
-
Reverse proxy / load balancer: When the app runs behind IIS, Nginx, or a cloud load balancer, the forwarded scheme and host are not visible to the app, and
UseHttpsRedirectionwill redirect to the wrong port. Insertapp.UseForwardedHeaders()immediately afterapp.Build()(and before the exception handler) and configure the forwarded headers options to trust the proxy range. -
Schema validation: Enforce a JSON schema validator on the
Orderscollection so that inserted and updated documents always match theOrdermodel. - Deployment: For cross-origin hosting, configure an explicit CORS policy that allows only the Blazor application’s origin.
-
Atomic auto-increment: The sample computes
orderIdfrom the highest existing value. For high-throughput inserts, switch to a dedicatedcounterscollection withFindOneAndUpdateto assignorderIdatomically.
Troubleshooting
| Symptom | Resolution |
|---|---|
| MongoDB service is not running | Start the MongoDB service (Start-Service MongoDB on Windows, or run mongod manually) and confirm it is listening on 27017. |
| MongoDB Compass connection failure | Confirm the connection string matches mongodb://localhost:27017. If authentication is enabled, include credentials and authSource in the URI. |
| Database not found | Create the OrderDB database (see Step 4) and confirm MongoDbSettings:DatabaseName matches. MongoDB creates a database lazily, but the sample read returns an empty result set until documents are inserted. |
| Collection not found | Create the Orders collection and confirm MongoDbSettings:CollectionName matches. |
| Pivot Table shows no data | Inspect POST /api/Order in the browser Network panel and check the server log. A GET request from the browser address bar returns 405. |
405 Method Not Allowed |
Confirm [HttpPost], AddControllers(), and MapControllers() are present. |
The MongoDB connection string is not configured. |
Add a ConnectionStrings:MongoDB entry to appsettings.json and restart the application. |
| Documents are inserted but fields appear as null | Confirm the [BsonElement] names match the document field names imported in Step 5 (orderId, customerName, employeeId, freight, shipCity). |
_id mapping failures |
Confirm the Order.Id property is decorated with [BsonId] and [BsonRepresentation(BsonType.ObjectId)]. Do not send _id from the Pivot Table; let MongoDB generate it. |
Update returns 404
|
Confirm that the supplied OrderID exists and that OrderID is marked as the primary key in the BeginDrillThrough event handler. |
Duplicate orderId on insert |
The sample computes orderId from the highest existing value. Concurrent inserts can collide; switch to an atomic counter document for high-throughput inserts. |
CRUD returns 400
|
Inspect the request JSON and confirm required fields (CustomerName, EmployeeID) and a numeric key are present. |
CRUD returns 500
|
Check the server log and verify the connection string, database and collection names, and MongoDB server status. |
| Browser reports mixed content or a redirect failure | Confirm Home.razor uses same-origin relative URLs (for example /api/Order) rather than hard-coded HTTP URLs. |
| Cross-origin request is blocked | Prefer same-origin relative URLs; otherwise configure AddCors and UseCors for the exact Blazor application origin. |
| Antiforgery validation fails | Configure the adaptor to send the expected request token, or use an appropriate non-cookie API authentication scheme. |
| Large datasets are slow | Process DataManagerRequest operations on the server (MongoDB Filter, Sort, Skip, and Limit) instead of returning the entire collection. |
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 walked through binding a MongoDB database to the Syncfusion Blazor Pivot Table using the URL Adaptor. You installed MongoDB Community Server and MongoDB Compass, created the OrderDB database and Orders collection, imported sample documents, and built an ASP.NET Core API that uses MongoDB.Driver to read and write those documents. The Blazor Pivot Table consumed the API through SfDataManager and the UrlAdaptor, configured OrderID as the primary key for drill-through editing, and performed read, insert, update, and delete operations through same-origin relative API URLs. Use this sample as a starting point for adding server-side filtering, sorting, paging, authentication, and schema validation before deploying to production.