
Syncfusion Blazor Datamanager
- 250 installs
- 4 repo stars
- Updated July 28, 2026
- syncfusion/blazor-ui-components-skills
Use syncfusion-blazor-datamanager for development tasks
About
syncfusion-blazor-datamanager: A skill for development. This provides functionality for development workflows.
- syncfusion-blazor-datamanager
Syncfusion Blazor Datamanager by the numbers
- 250 all-time installs (skills.sh)
- +14 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,533 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/blazor-ui-components-skills --skill syncfusion-blazor-datamanagerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 250 |
|---|---|
| repo stars | ★ 4 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/blazor-ui-components-skills ↗ |
What it does
Use syncfusion-blazor-datamanager for development tasks
Files
Syncfusion Blazor DataManager
The Syncfusion Blazor SfDataManager is the data access layer for all Syncfusion data-bound components. It acts as a gateway between your data source — local in-memory collections or remote REST/OData/GraphQL services — and components like SfGrid, SfDropDownList, and others. It handles querying, sorting, filtering, paging, and CRUD operations through a configurable adaptor model.
When to Use This Skill
- Setting up
SfDataManagerin a new Blazor project (WASM or Web App) - Binding local JSON/list data to a Syncfusion component via the
Jsonproperty - Connecting to a trusted and authenticated remote service (OData, Web API, GraphQL) via
Url+Adaptor - Choosing the right adaptor for a given backend service
- Implementing CRUD operations using remote adaptors or custom adaptors
- Creating a custom adaptor by deriving from
DataAdaptor - Adding custom HTTP headers (with authentication tokens) to DataManager requests
- Enabling offline mode (client-side query processing after one-time remote fetch)
⚠️ Critical Security Requirements
When binding to remote services, you MUST implement these protections:
1. Use HTTPS only — never use unencrypted HTTP connections; encrypt all data in transit 2. Whitelist trusted endpoints — store approved URLs in a HashSet<string> or configuration file; validate all URLs before assignment; never accept dynamic URLs from user input, query parameters, or untrusted sources 3. Use string variables for URLs — assign endpoints to private string properties rather than hardcoding in markup; this enables centralized validation and easier maintenance 4. Validate endpoints on component initialization — use OnInitialized() lifecycle method to verify endpoints against whitelist before binding 5. Validate and sanitize responses — implement server-side schema validation; reject unexpected response formats; map responses to strongly-typed models 6. Monitor remote calls — log all external API requests with timestamps, endpoints, and outcomes for security audit trails 7. Implement CORS securely — use specific trusted origins only; never use wildcard * in production; require HTTPS 8. Prevent indirect prompt injection attacks — verify endpoint ownership; implement request signing; validate response content types and schemas before consuming data
Third-party API responses can introduce security risks. Always verify endpoint legitimacy, authenticate requests, and validate data before binding to UI components.
Component Overview
| Property | Purpose |
|---|---|
Json | Bind in-memory collection (local data) |
Url | Remote service endpoint |
Adaptor | Specifies how requests/responses are processed |
AdaptorInstance | Type reference for CustomAdaptor |
Headers | Custom HTTP headers for all outbound requests |
Offline | Enable client-side processing after initial remote fetch |
GraphQLAdaptorOptions | Query/mutation configuration for GraphQL services |
Documentation and Navigation Guide
Getting Started
📄 Read: references/getting-started.md
- NuGet installation (
Syncfusion.Blazor.Data+Syncfusion.Blazor.Themes) - Project setup for Blazor WebAssembly and Blazor Web App
- Namespace imports and service registration in
Program.cs - Stylesheet and script references
- Binding to local JSON data with
SfGrid - Binding to OData remote service
- Binding to
SfDropDownList(local and remote)
Data Binding
📄 Read: references/data-binding.md
- Local data binding:
Jsonproperty with in-memory collections - Remote data binding:
Url+Adaptorproperties - Key benefits of each approach (when to choose local vs remote)
- Client-side vs server-side operation execution
- Practical
SfGridandSfDropDownListexamples
Adaptors
📄 Read: references/adaptors.md
- Adaptor selection guide (which adaptor to use for which backend)
UrlAdaptor— base adaptor for custom REST servicesODataAdaptor— OData v3 protocolODataV4Adaptor— OData v4 protocolWebApiAdaptor— Web API endpoints with OData query support- Expected server response formats (
result+count)
GraphQL Adaptor
📄 Read: references/graphql-adaptor.md
GraphQLAdaptorOptionsconfiguration (Query,ResolverName)- Fetching and displaying data from a GraphQL service
- CRUD mutations:
Insert,Update,Delete - Batch editing via the
Batchmutation property DataManagerRequestmodel used by server resolvers- Server-side
Program.csconfiguration (schema, CORS)
Custom Binding
📄 Read: references/custom-binding.md
- When to use a custom adaptor (non-standard sources, custom business rules)
DataAdaptorabstract class — available virtual methods- Overriding
Read/ReadAsyncwithDataOperationshelper methods - Implementing CRUD:
Insert,Update,Remove,BatchUpdate - Full
CustomAdaptorimplementation withSfGridexample
How-To Guides
📄 Read: references/how-to.md
- Adding custom HTTP headers (authentication tokens, tenant IDs)
- Enabling offline mode for client-side query processing
- When to use offline mode and which adaptors it supports
Quick Start Example
Local Data Binding
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="EmployeeData" ID="Grid">
<SfDataManager Json="@Employees"></SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(EmployeeData.EmployeeID)" HeaderText="Employee ID" Width="120"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.Name)" HeaderText="First Name" Width="130"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.Title)" HeaderText="Title" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class EmployeeData
{
public int EmployeeID { get; set; }
public string Name { get; set; }
public string Title { get; set; }
}
public List<EmployeeData> Employees = new()
{
new EmployeeData { EmployeeID = 1, Name = "Nancy Fuller", Title = "Vice President" },
new EmployeeData { EmployeeID = 2, Name = "Steven Buchanan", Title = "Sales Manager" }
};
}Remote Data Binding (OData)
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<!-- SECURITY: Use string variables for endpoints, validate against whitelist, use HTTPS only -->
<SfGrid TValue="Order" AllowPaging="true">
<SfDataManager Url="@ODataEndpointUrl"
Adaptor="Adaptors.ODataAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(Order.OrderID)" HeaderText="Order ID" Width="120"></GridColumn>
<GridColumn Field="@nameof(Order.CustomerID)" HeaderText="Customer Name" Width="150"></GridColumn>
</GridColumns>
</SfGrid>
@code {
// Whitelist of trusted OData endpoints — define in appsettings.json in production
private static readonly HashSet<string> TrustedEndpoints = new()
{
"https://api.yourtrusted-domain.com/odata/"
};
// Assigned string variable for endpoint
private string ODataEndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Validate and assign endpoint from configuration
const string endpointBase = "https://api.yourtrusted-domain.com/odata/Orders";
if (!TrustedEndpoints.Any(trusted => endpointBase.StartsWith(trusted)))
throw new InvalidOperationException($"Security validation failed: untrusted endpoint '{endpointBase}'");
ODataEndpointUrl = endpointBase;
}
public class Order
{
public int? OrderID { get; set; }
public string? CustomerID { get; set; }
}
}Common Patterns
Choosing the Right Adaptor
| Backend Type | Adaptor to Use |
|---|---|
Custom REST API returning { result, count } | Adaptors.UrlAdaptor |
| OData v3 service | Adaptors.ODataAdaptor |
| OData v4 service | Adaptors.ODataV4Adaptor |
| ASP.NET Web API with OData query support | Adaptors.WebApiAdaptor |
| GraphQL service | Adaptors.GraphQLAdaptor |
| Non-standard source / full custom control | Adaptors.CustomAdaptor |
Placing SfDataManager Inside a Component
SfDataManager is always placed as a child component inside the data-bound Syncfusion component:
<SfGrid TValue="MyModel">
<SfDataManager Url="..." Adaptor="Adaptors.WebApiAdaptor"></SfDataManager>
<GridColumns>...</GridColumns>
</SfGrid>Enabling Editing with Custom Adaptor
When using CustomAdaptor with CRUD operations, configure the Grid toolbar and edit settings alongside the adaptor:
<SfGrid TValue="Order" Toolbar="@(new List<string>() { "Add", "Delete", "Update", "Cancel" })">
<SfDataManager AdaptorInstance="@typeof(CustomAdaptor)" Adaptor="Adaptors.CustomAdaptor"></SfDataManager>
<GridEditSettings AllowEditing="true" AllowDeleting="true" AllowAdding="true" Mode="@EditMode.Normal"></GridEditSettings>
...
</SfGrid>Adaptors in Blazor DataManager
Adaptors tell SfDataManager how to translate data operations into requests for a specific service type, and how to parse the responses back. Each remote service type has a matching built-in adaptor.
Table of Contents
- Adaptor Selection Guide
- UrlAdaptor
- ODataAdaptor
- ODataV4Adaptor
- WebApiAdaptor
- CustomAdaptor
- Server Response Format
---
Adaptor Selection Guide
Choose the adaptor that matches your backend:
| Backend / Service Type | Adaptor |
|---|---|
Custom REST API that returns { result, count } | Adaptors.UrlAdaptor |
| OData v3 endpoint | Adaptors.ODataAdaptor |
| OData v4 endpoint | Adaptors.ODataV4Adaptor |
| ASP.NET Web API with OData query option support | Adaptors.WebApiAdaptor |
| GraphQL service | Adaptors.GraphQLAdaptor |
| Non-standard source requiring full custom logic | Adaptors.CustomAdaptor |
TheAdaptorproperty is set onSfDataManager.GraphQLAdaptorandCustomAdaptorrequire additional configuration — see the dedicated reference files for those.
---
UrlAdaptor
The UrlAdaptor is the base adaptor for remote services. Use it when connecting to any REST endpoint that does not implement OData or GraphQL, but returns data in the expected { result, count } JSON format.
Key points:
- Converts DataManager query operations into HTTP requests
- Server must return a JSON object with
result(data array) andcount(total records) - Supports paging, sorting, and filtering through query parameters
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="EmployeeData" ID="Grid" AllowPaging="true">
<SfDataManager Url="https://blazor.syncfusion.com/services/production/api/gridurldata"
Adaptor="Adaptors.UrlAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(EmployeeData.EmployeeID)" HeaderText="Employee ID"
TextAlign="TextAlign.Center" Width="120"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.EmployeeName)" HeaderText="First Name" Width="130"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.Designation)" HeaderText="Title" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class EmployeeData
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public string Designation { get; set; }
}
}Required server response format:
{
"result": [{ "EmployeeID": 1, "EmployeeName": "Nancy", "Designation": "VP" }],
"count": 67
}---
ODataAdaptor
Use ODataAdaptor when the service implements the OData v3 protocol. It automatically generates OData-compliant query parameters for paging ($top, $skip), sorting ($orderby), and filtering ($filter).
Key points:
- Ideal for services exposing standard OData v3 endpoints
- Automatically formats queries per OData spec
- Requires
{ result, count }response structure
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="OrderData" ID="OrdersGrid" AllowPaging="true">
<SfDataManager Url="https://services.odata.org/Northwind/Northwind.svc/Orders"
Adaptor="Adaptors.ODataAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(OrderData.OrderID)" HeaderText="Order ID"
TextAlign="TextAlign.Center" Width="120"></GridColumn>
<GridColumn Field="@nameof(OrderData.CustomerID)" HeaderText="Customer Name" Width="130"></GridColumn>
<GridColumn Field="@nameof(OrderData.EmployeeID)" HeaderText="Employee ID" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class OrderData
{
public int OrderID { get; set; }
public string? CustomerID { get; set; }
public int EmployeeID { get; set; }
}
}---
ODataV4Adaptor
Use ODataV4Adaptor when the service implements OData v4, which offers advanced query capabilities like $apply for aggregation. Configuration is identical to ODataAdaptor — only the adaptor value differs.
Key points:
- Implements OData v4 protocol for standardized access
- Automatically generates v4-compliant query parameters
- Use when your service URL includes
/V4/or explicitly supports OData v4
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="EmployeeData" ID="Grid" AllowPaging="true">
<SfDataManager Url="https://services.odata.org/V4/Northwind/Northwind.svc/Orders/"
Adaptor="Adaptors.ODataV4Adaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field=@nameof(EmployeeData.OrderID) TextAlign="TextAlign.Center"
HeaderText="Order ID" Width="120"></GridColumn>
<GridColumn Field=@nameof(EmployeeData.CustomerID) TextAlign="TextAlign.Center"
HeaderText="Customer Name" Width="130"></GridColumn>
<GridColumn Field=@nameof(EmployeeData.EmployeeID) TextAlign="TextAlign.Center"
HeaderText="Employee ID" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class EmployeeData
{
public int OrderID { get; set; }
public string CustomerID { get; set; }
public int EmployeeID { get; set; }
}
}---
WebApiAdaptor
Use WebApiAdaptor for ASP.NET Web API endpoints that understand OData query options. It extends ODataAdaptor to work with Web API controllers that parse OData-style query strings.
Key points:
- Works with Web API endpoints that accept OData query options (
$top,$skip,$filter,$orderby) - Server must return
{ result, count }JSON format - Most common adaptor for ASP.NET Core backend + Syncfusion Grid scenarios
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="Order" AllowPaging="true">
<SfDataManager Url="https://blazor.syncfusion.com/services/production/api/Orders/"
Adaptor="Adaptors.WebApiAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(Order.OrderID)" HeaderText="Order ID" IsPrimaryKey="true"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
<GridColumn Field="@nameof(Order.CustomerID)" HeaderText="Customer Name" Width="150"></GridColumn>
<GridColumn Field="@nameof(Order.OrderDate)" HeaderText="Order Date" Format="d"
Type="ColumnType.Date" TextAlign="TextAlign.Right" Width="130"></GridColumn>
<GridColumn Field="@nameof(Order.Freight)" HeaderText="Freight" Format="C2"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class Order
{
public int? OrderID { get; set; }
public string CustomerID { get; set; }
public DateTime? OrderDate { get; set; }
public double? Freight { get; set; }
}
}Expected server response:
{
"result": [{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 }],
"count": 830
}---
CustomAdaptor
When none of the built-in adaptors fit your requirements, implement a custom adaptor by deriving from DataAdaptor. Assign the type to AdaptorInstance and set Adaptor to Adaptors.CustomAdaptor.
<SfDataManager AdaptorInstance="@typeof(MyCustomAdaptor)" Adaptor="Adaptors.CustomAdaptor">
</SfDataManager>For full implementation details, see references/custom-binding.md.---
Server Response Format
All remote adaptors (Url, OData, ODataV4, WebApi) expect the server to return a JSON object with these properties when paging is enabled:
| Property | Type | Description |
|---|---|---|
result | Array | The current page of records |
count | Number | Total number of records across all pages |
{
"result": [{ "OrderID": 10248 }, { "OrderID": 10249 }],
"count": 830
}This structure allows SfDataManager to correctly configure pager controls and virtual scrolling in the bound component.
Custom Binding in Blazor DataManager
Custom binding lets you implement your own data retrieval and manipulation logic by creating a class that inherits from DataAdaptor. Use this when built-in adaptors don't fit your data source or when you need custom business rules during CRUD operations.
Table of Contents
- When to Use Custom Binding
- DataAdaptor Abstract Class
- Performing Data Operations (Read)
- Performing CRUD Operations
- Full Implementation Example
---
When to Use Custom Binding
Custom binding is the right choice when:
- Data comes from a source that doesn't match any built-in adaptor (e.g., a non-standard REST API, a local database via EF Core, a third-party SDK)
- You need to apply custom business rules during CRUD operations (e.g., validation before insert, audit logging on delete)
- You want full control over how filtering, sorting, and paging are applied
---
DataAdaptor Abstract Class
DataAdaptor is the abstract base class you derive from. Override the methods relevant to your use case:
public abstract class DataAdaptor
{
// Read operations
public virtual object Read(DataManagerRequest dataManagerRequest, string key = null)
public virtual Task<object> ReadAsync(DataManagerRequest dataManagerRequest, string key = null)
// Insert operations
public virtual object Insert(DataManager dataManager, object data, string key)
public virtual Task<object> InsertAsync(DataManager dataManager, object data, string key)
// Update operations
public virtual object Update(DataManager dataManager, object data, string keyField, string key)
public virtual Task<object> UpdateAsync(DataManager dataManager, object data, string keyField, string key)
// Delete operations
public virtual object Remove(DataManager dataManager, object data, string keyField, string key)
public virtual Task<object> RemoveAsync(DataManager dataManager, object data, string keyField, string key)
// Batch CRUD
public virtual object BatchUpdate(DataManager dataManager, object changedRecords, object addedRecords,
object deletedRecords, string keyField, string key, int? dropIndex)
public virtual Task<object> BatchUpdateAsync(DataManager dataManager, object changedRecords,
object addedRecords, object deletedRecords, string keyField, string key, int? dropIndex)
}You only need to override the methods your scenario requires. If Read/ReadAsync is not overridden, the default handler processes the request.
---
Performing Data Operations (Read)
Override Read or ReadAsync to apply searching, sorting, filtering, and paging using the built-in DataOperations helper class. The DataManagerRequest parameter provides all the query details.
DataOperations methods available:
| Method | What it does |
|---|---|
DataOperations.PerformSearching(source, search) | Apply text search |
DataOperations.PerformSorting(source, sorted) | Apply sort descriptors |
DataOperations.PerformFiltering(source, where, operator) | Apply filter conditions |
DataOperations.PerformSkip(source, skip) | Skip records for paging |
DataOperations.PerformTake(source, take) | Take records for paging |
DataUtil.PerformAggregation(source, aggregates) | Calculate sum/avg/min/max |
Return type rules:
- When
dm.RequiresCounts == true→ return aDataResultobject withResult(data) andCount(total) - When
dm.RequiresCounts == false→ return the collection directly
public override object Read(DataManagerRequest dm, string key = null)
{
IEnumerable<Order> dataSource = Orders;
if (dm.Search?.Count > 0)
dataSource = DataOperations.PerformSearching(dataSource, dm.Search);
if (dm.Sorted?.Count > 0)
dataSource = DataOperations.PerformSorting(dataSource, dm.Sorted);
if (dm.Where?.Count > 0)
dataSource = DataOperations.PerformFiltering(dataSource, dm.Where, dm.Where[0].Operator);
int count = dataSource.Count();
if (dm.Skip != 0)
dataSource = DataOperations.PerformSkip(dataSource, dm.Skip);
if (dm.Take != 0)
dataSource = DataOperations.PerformTake(dataSource, dm.Take);
return dm.RequiresCounts
? new DataResult() { Result = dataSource, Count = count }
: (object)dataSource;
}---
Performing CRUD Operations
Override the relevant methods to handle data changes. The SfGrid triggers these automatically when editing is configured.
public override object Insert(DataManager dm, object value, string key)
{
Orders.Insert(0, value as Order);
return value;
}
public override object Remove(DataManager dm, object value, string keyField, string key)
{
Orders.Remove(Orders.FirstOrDefault(o => o.OrderID == int.Parse(value.ToString())));
return value;
}
public override object Update(DataManager dm, object value, string keyField, string key)
{
var existing = Orders.FirstOrDefault(o => o.OrderID == (value as Order).OrderID);
if (existing != null)
{
existing.CustomerID = (value as Order).CustomerID;
existing.Freight = (value as Order).Freight;
}
return value;
}
public override object BatchUpdate(DataManager dm, object changed, object added, object deleted,
string keyField, string key, int? dropIndex)
{
if (changed is IEnumerable<Order> changedRecords)
foreach (var rec in changedRecords)
{
var existing = Orders.FirstOrDefault(o => o.OrderID == rec.OrderID);
if (existing != null) existing.CustomerID = rec.CustomerID;
}
if (added is IEnumerable<Order> addedRecords)
foreach (var rec in addedRecords)
Orders.Add(rec);
if (deleted is IEnumerable<Order> deletedRecords)
foreach (var rec in deletedRecords)
Orders.RemoveAll(o => o.OrderID == rec.OrderID);
return Orders;
}---
Full Implementation Example
This example shows the complete pattern: the custom adaptor class defined inline in the Razor component, bound to a Grid with full CRUD support.
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="Order" ID="Grid" AllowSorting="true" AllowFiltering="true" AllowPaging="true"
Toolbar="@(new List<string>() { "Add", "Delete", "Update", "Cancel" })">
<SfDataManager AdaptorInstance="@typeof(CustomAdaptor)" Adaptor="Adaptors.CustomAdaptor">
</SfDataManager>
<GridPageSettings PageSize="8"></GridPageSettings>
<GridEditSettings AllowEditing="true" AllowDeleting="true" AllowAdding="true"
Mode="@EditMode.Normal">
</GridEditSettings>
<GridColumns>
<GridColumn Field="@nameof(Order.OrderID)" HeaderText="Order ID" IsPrimaryKey="true"
TextAlign="TextAlign.Center" Width="140"></GridColumn>
<GridColumn Field="@nameof(Order.CustomerID)" HeaderText="Customer Name" Width="150"></GridColumn>
<GridColumn Field="@nameof(Order.Freight)" HeaderText="Freight" Width="150"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public static List<Order> Orders { get; set; } = new();
protected override void OnInitialized()
{
Orders = Enumerable.Range(1, 75).Select(x => new Order()
{
OrderID = 1000 + x,
CustomerID = new[] { "ALFKI", "ANANTR", "ANTON", "BLONP", "BOLID" }[new Random().Next(5)],
Freight = 2.1 * x
}).ToList();
}
public class Order
{
public int OrderID { get; set; }
public string? CustomerID { get; set; }
public double Freight { get; set; }
}
public class CustomAdaptor : DataAdaptor
{
public override object Read(DataManagerRequest dm, string key = null)
{
IEnumerable<Order> dataSource = Orders;
if (dm.Search?.Count > 0)
dataSource = DataOperations.PerformSearching(dataSource, dm.Search);
if (dm.Sorted?.Count > 0)
dataSource = DataOperations.PerformSorting(dataSource, dm.Sorted);
if (dm.Where?.Count > 0)
dataSource = DataOperations.PerformFiltering(dataSource, dm.Where, dm.Where[0].Operator);
int count = dataSource.Count();
if (dm.Skip != 0)
dataSource = DataOperations.PerformSkip(dataSource, dm.Skip);
if (dm.Take != 0)
dataSource = DataOperations.PerformTake(dataSource, dm.Take);
return dm.RequiresCounts
? new DataResult() { Result = dataSource, Count = count }
: (object)dataSource;
}
public override object Insert(DataManager dm, object value, string key)
{
Orders.Insert(0, value as Order);
return value;
}
public override object Remove(DataManager dm, object value, string keyField, string key)
{
Orders.Remove(Orders.FirstOrDefault(o => o.OrderID == int.Parse(value.ToString())));
return value;
}
public override object Update(DataManager dm, object value, string keyField, string key)
{
var data = Orders.FirstOrDefault(o => o.OrderID == (value as Order).OrderID);
if (data != null)
{
data.CustomerID = (value as Order).CustomerID;
data.Freight = (value as Order).Freight;
}
return value;
}
public override object BatchUpdate(DataManager dm, object changed, object added,
object deleted, string keyField, string key, int? dropIndex)
{
if (changed is IEnumerable<Order> changedRecords)
foreach (var rec in changedRecords)
{
var existing = Orders.FirstOrDefault(o => o.OrderID == rec.OrderID);
if (existing != null) existing.CustomerID = rec.CustomerID;
}
if (added is IEnumerable<Order> addedRecords)
foreach (var rec in addedRecords)
Orders.Add(rec);
if (deleted is IEnumerable<Order> deletedRecords)
foreach (var rec in deletedRecords)
Orders.RemoveAll(o => o.OrderID == rec.OrderID);
return Orders;
}
}
}Key configuration points:
AdaptorInstance="@typeof(CustomAdaptor)"— references the adaptor class typeAdaptor="Adaptors.CustomAdaptor"— tells DataManager to use the custom pathIsPrimaryKey="true"on the key column — required for Update and Remove operations to work correctlyGridEditSettings— required for CRUD; setModebased on your UX preference (Normal,Dialog,Batch)
Data Binding in Blazor DataManager
The SfDataManager supports two primary data binding approaches: local (in-memory) and remote (external service). Choose based on dataset size, update frequency, and where you want operations like sorting and filtering to execute.
Table of Contents
---
Local Data Binding
Local data binding connects a component to data already loaded in application memory. Assign the in-memory collection to the Json property of SfDataManager.
When to use local binding:
- Dataset is already available in memory (fetched once, doesn't change often)
- Small to medium-sized collections
- You want all operations (filtering, sorting, paging, grouping) to run in the browser without network calls
- No external service is involved
Key benefits:
- No network latency for subsequent operations
- Simple configuration — no adaptor needed
- All data operations execute client-side automatically
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="EmployeeData" ID="Grid">
<SfDataManager Json="@Employees"></SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(EmployeeData.EmployeeID)" HeaderText="Employee ID"
TextAlign="TextAlign.Center" Width="120"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.Name)" HeaderText="First Name" Width="130"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.Title)" HeaderText="Title" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class EmployeeData
{
public int EmployeeID { get; set; }
public string Name { get; set; }
public string Title { get; set; }
}
private List<EmployeeData> Employees { get; set; } = new()
{
new EmployeeData { EmployeeID = 1, Name = "Nancy Fuller", Title = "Vice President" },
new EmployeeData { EmployeeID = 2, Name = "Steven Buchanan", Title = "Sales Manager" },
new EmployeeData { EmployeeID = 3, Name = "Janet Leverling", Title = "Sales Representative" },
new EmployeeData { EmployeeID = 4, Name = "Andrew Davolio", Title = "Inside Sales Coordinator" },
new EmployeeData { EmployeeID = 5, Name = "Steven Peacock", Title = "Inside Sales Coordinator" }
};
}---
Remote Data Binding
Remote data binding connects a component to data hosted on an external service. Set the Url property to the service endpoint and specify the Adaptor property so SfDataManager knows how to format requests and parse responses.
When to use remote binding:
- Large datasets that should not be fully loaded into memory
- Data that changes frequently and must be fetched dynamically
- Server-side filtering, sorting, and paging for performance
Key benefits:
- Integrates with external APIs and services
- Supports large-scale data without memory overhead
- Operations like filtering and paging can execute server-side
⚠️ CRITICAL SECURITY REQUIREMENT: Remote endpoints must be:
- Trusted and authenticated — only use services you control or have contractually verified
- HTTPS only — enforce encrypted communication
- Validated on every request — whitelist allowed URLs in your application configuration
- Monitored for suspicious activity — log all remote requests
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<!-- SECURITY: Use string variable for endpoint URL with whitelist validation -->
<SfGrid TValue="Order" ID="Grid" AllowPaging="true">
<SfDataManager Url="@RemoteODataEndpointUrl"
Adaptor="Adaptors.ODataAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(Order.OrderID)" HeaderText="Order ID" IsPrimaryKey="true"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
<GridColumn Field="@nameof(Order.CustomerID)" HeaderText="Customer Name" Width="150"></GridColumn>
<GridColumn Field="@nameof(Order.OrderDate)" HeaderText="Order Date" Format="d"
Type="ColumnType.Date" TextAlign="TextAlign.Right" Width="130"></GridColumn>
<GridColumn Field="@nameof(Order.Freight)" HeaderText="Freight" Format="C2"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
// Whitelist of trusted OData endpoints
private static readonly HashSet<string> TrustedODataEndpoints = new()
{
"https://api.yourtrusted-domain.com/odata/"
};
// String variable for endpoint URL
private string RemoteODataEndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://api.yourtrusted-domain.com/odata/Orders";
if (!TrustedODataEndpoints.Any(trusted => endpoint.StartsWith(trusted)))
throw new InvalidOperationException($"Security validation failed: endpoint '{endpoint}' is not in the trusted list");
RemoteODataEndpointUrl = endpoint;
}
public class Order
{
public int? OrderID { get; set; }
public string CustomerID { get; set; }
public DateTime? OrderDate { get; set; }
public double? Freight { get; set; }
}
}---
Choosing Local vs Remote
| Criteria | Local Binding (Json) | Remote Binding (Url + Adaptor) |
|---|---|---|
| Data location | Already in memory | External service/API |
| Dataset size | Small to medium | Any (including large) |
| Network requests | None after initial load | Per operation (or server-side batch) |
| Operations | Client-side | Server-side or client-side |
| Configuration complexity | Minimal | Requires matching adaptor |
| Offline support | Yes (data already in memory) | Use Offline="true" property |
When you have a remote service but want to avoid repeated network requests, enableOffline="true"onSfDataManager. This fetches the full dataset once and processes all subsequent operations client-side.
---
SfGrid Examples
Both examples below produce the same Grid UI — the difference is where data comes from and where operations run.
Local — full collection in @code
<SfGrid TValue="Product">
<SfDataManager Json="@Products"></SfDataManager>
<GridColumns>
<GridColumn Field="ProductID" HeaderText="ID" Width="80"></GridColumn>
<GridColumn Field="ProductName" HeaderText="Product" Width="150"></GridColumn>
<GridColumn Field="Price" HeaderText="Price" Format="C2" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class Product { public int ProductID; public string ProductName; public double Price; }
public List<Product> Products = new() { /* ... */ };
}Remote — data fetched from Web API
<SfGrid TValue="Product" AllowPaging="true">
<!-- SECURITY: Use string variable for endpoint URL with whitelist validation -->
<SfDataManager Url="@WebApiEndpointUrl"
Adaptor="Adaptors.WebApiAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="ProductID" HeaderText="ID" Width="80"></GridColumn>
<GridColumn Field="ProductName" HeaderText="Product" Width="150"></GridColumn>
<GridColumn Field="Price" HeaderText="Price" Format="C2" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
// Whitelist of trusted Web API endpoints
private static readonly HashSet<string> TrustedWebApiEndpoints = new()
{
"https://api.yourtrusted-domain.com/api/"
};
// String variable for endpoint URL
private string WebApiEndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://api.yourtrusted-domain.com/api/products";
if (!TrustedWebApiEndpoints.Any(trusted => endpoint.StartsWith(trusted)))
throw new InvalidOperationException($"Security validation failed: endpoint '{endpoint}' is not in the trusted list");
WebApiEndpointUrl = endpoint;
}
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
}
}Getting Started with Blazor DataManager
This guide covers setting up SfDataManager in a new Blazor project and rendering your first data-bound component.
Table of Contents
- Prerequisites
- Install NuGet Packages
- Configure the Application
- Add Stylesheet and Script Resources
- Binding to JSON Data
- Binding to OData (Remote)
- Component Binding with SfDropDownList
---
Prerequisites
Ensure the development environment meets the system requirements for Syncfusion Blazor components before proceeding (.NET 9.0 or later recommended).
---
Install NuGet Packages
Install the following packages in your Blazor project:
Syncfusion.Blazor.Data— DataManager coreSyncfusion.Blazor.Themes— Theme CSS files
Visual Studio (Package Manager Console):
Install-Package Syncfusion.Blazor.Data
Install-Package Syncfusion.Blazor.Themesdotnet CLI:
dotnet add package Syncfusion.Blazor.Data
dotnet add package Syncfusion.Blazor.Themes
dotnet restoreFor Blazor Web App with WebAssembly or Auto render mode, install these packages in the Client project.
---
Configure the Application
Import Namespaces
Add to ~/_Imports.razor:
@using Syncfusion.Blazor
@using Syncfusion.Blazor.DataRegister Syncfusion Service
Blazor WebAssembly — `~/Program.cs`:
using Syncfusion.Blazor;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddSyncfusionBlazor();
await builder.Build().RunAsync();Blazor Web App (Auto or WebAssembly render mode) — Server `Program.cs`:
using Syncfusion.Blazor;
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddSyncfusionBlazor();Blazor Web App (Auto or WebAssembly render mode) — Client `Program.cs`:
using Syncfusion.Blazor;
builder.Services.AddSyncfusionBlazor();
await builder.Build().RunAsync();Blazor Web App (Server render mode only) — `Program.cs`:
using Syncfusion.Blazor;
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddSyncfusionBlazor();---
Add Stylesheet and Script Resources
Blazor WebAssembly — ~/wwwroot/index.html
<head>
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
<script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js" type="text/javascript"></script>
</head>Blazor Web App — ~/Components/App.razor
<!-- In <head> -->
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
<!-- At end of <body> -->
<script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js" type="text/javascript"></script>Available themes: bootstrap5.css, material.css, fluent.css, tailwind.css, fabric.css, material-dark.css
---
Binding to JSON Data
Use the Json property of SfDataManager to bind an in-memory collection. The SfDataManager is placed as a child inside the data-bound component.
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<SfGrid TValue="EmployeeData" ID="Grid">
<SfDataManager Json="@Employees"></SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(EmployeeData.EmployeeID)" TextAlign="TextAlign.Center"
HeaderText="Employee ID" Width="120"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.Name)" HeaderText="First Name" Width="130"></GridColumn>
<GridColumn Field="@nameof(EmployeeData.Title)" HeaderText="Title" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
public class EmployeeData
{
public int EmployeeID { get; set; }
public string Name { get; set; }
public string Title { get; set; }
}
public List<EmployeeData> Employees = new()
{
new EmployeeData { EmployeeID = 1, Name = "Nancy Fuller", Title = "Vice President" },
new EmployeeData { EmployeeID = 2, Name = "Steven Buchanan", Title = "Sales Manager" },
new EmployeeData { EmployeeID = 3, Name = "Janet Leverling", Title = "Sales Representative" },
new EmployeeData { EmployeeID = 4, Name = "Andrew Davolio", Title = "Inside Sales Coordinator" }
};
}---
Binding to OData (Remote)
Set Url to the service endpoint and Adaptor to the matching adaptor type. Always use string variables for endpoints and validate against a whitelist before binding.
⚠️ SECURITY WARNING: Only connect to trusted, authenticated services. Use HTTPS, validate endpoints against a whitelist, and sanitize all API responses to prevent injection attacks. See Security Best Practices below.
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<!-- SECURITY: Use string variable for endpoint URL with whitelist validation -->
<SfGrid TValue="Order" ID="Grid" AllowPaging="true">
<SfDataManager Url="@ODataEndpointUrl"
Adaptor="Adaptors.ODataAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(Order.OrderID)" HeaderText="Order ID" IsPrimaryKey="true"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
<GridColumn Field="@nameof(Order.CustomerID)" HeaderText="Customer Name" Width="150"></GridColumn>
<GridColumn Field="@nameof(Order.OrderDate)" HeaderText="Order Date" Format="d"
Type="ColumnType.Date" TextAlign="TextAlign.Right" Width="130"></GridColumn>
<GridColumn Field="@nameof(Order.Freight)" HeaderText="Freight" Format="C2"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
// Whitelist of trusted OData endpoints
private static readonly HashSet<string> TrustedODataEndpoints = new()
{
"https://services.odata.org/Northwind/Northwind.svc/",
"https://api.yourtrusted-domain.com/odata/"
};
// String variable for endpoint URL
private string ODataEndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://services.odata.org/Northwind/Northwind.svc/Orders";
if (!TrustedODataEndpoints.Any(trusted => endpoint.StartsWith(trusted)))
throw new InvalidOperationException($"Security validation failed: endpoint '{endpoint}' is not in the trusted list");
ODataEndpointUrl = endpoint;
}
public class Order
{
public int? OrderID { get; set; }
public string? CustomerID { get; set; }
public DateTime? OrderDate { get; set; }
public double? Freight { get; set; }
}
}---
Component Binding with SfDropDownList
SfDataManager works with any Syncfusion data-bound component, not just SfGrid.
Local Data
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.DropDowns
<SfDropDownList Placeholder="e.g. Australia" TItem="Country" TValue="string">
<SfDataManager Json="@Countries"></SfDataManager>
<DropDownListFieldSettings Value="Name"></DropDownListFieldSettings>
</SfDropDownList>
@code {
public class Country
{
public string? Name { get; set; }
public string? Code { get; set; }
}
public List<Country> Countries = new()
{
new Country { Name = "Australia", Code = "AU" },
new Country { Name = "Bermuda", Code = "BM" },
new Country { Name = "Canada", Code = "CA" },
new Country { Name = "Cameroon", Code = "CM" }
};
}Remote Data
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.DropDowns
<!-- SECURITY: Use string variable for endpoint URL with whitelist validation -->
<SfDropDownList Placeholder="Name" TItem="Contact" TValue="Contact">
<SfDataManager Url="@ODataV4EndpointUrl"
Adaptor="Adaptors.ODataV4Adaptor">
</SfDataManager>
<DropDownListFieldSettings Value="CustomerID" Text="ContactName"></DropDownListFieldSettings>
</SfDropDownList>
@code {
// Whitelist of trusted OData v4 endpoints
private static readonly HashSet<string> TrustedODataV4Endpoints = new()
{
"https://services.odata.org/V4/Northwind/Northwind.svc/",
"https://api.yourtrusted-domain.com/odata/v4/"
};
// String variable for endpoint URL
private string ODataV4EndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://services.odata.org/V4/Northwind/Northwind.svc/Customers";
if (!TrustedODataV4Endpoints.Any(trusted => endpoint.StartsWith(trusted)))
throw new InvalidOperationException($"Security validation failed: endpoint '{endpoint}' is not in the trusted list");
ODataV4EndpointUrl = endpoint;
}
public class Contact
{
public string? ContactName { get; set; }
public string? CustomerID { get; set; }
}
}---
Security Best Practices
Trust Only Known Endpoints
When using remote data binding with Url and adaptors, always:
- ✅ Use HTTPS for all remote endpoints
- ✅ Authenticate requests via tokens or custom headers (see Adding Custom Headers)
- ✅ Validate endpoints — whitelist only approved service URLs in your application
- ✅ Implement server-side validation on your backend to validate incoming requests
- ❌ Never accept arbitrary user input as the
Urlvalue (e.g., from query parameters or user text fields) - ❌ Never use unsigned/untrusted APIs without verification
Prevent Indirect Prompt Injection
Third-party API responses can introduce malicious content. Mitigate by:
1. Bind to trusted services only — verify endpoint ownership and reputation 2. Implement response validation — validate the schema and content types 3. Use model binding — ensure strongly-typed models (TValue) for data validation 4. Sanitize rendered content — if displaying user-generated data, use Blazor's built-in XSS protection 5. Monitor and log requests — track all remote data fetches for audit trails
Example: Validated Remote Binding
// In Program.cs — define trusted endpoints
var trustedEndpoints = new HashSet<string>
{
"https://services.odata.org/Northwind/Northwind.svc/",
"https://api.mycompany.com/data/",
};
// Validate before use
public static bool IsEndpointTrusted(string url)
{
var uri = new Uri(url);
return trustedEndpoints.Any(endpoint => uri.AbsoluteUri.StartsWith(endpoint));
}Then in your component:
@code {
private string RemoteUrl = "https://services.odata.org/Northwind/Northwind.svc/Orders";
protected override void OnInitialized()
{
if (!IsEndpointTrusted(RemoteUrl))
throw new InvalidOperationException("Untrusted endpoint");
}
private static bool IsEndpointTrusted(string url)
{
var trustedEndpoints = new[] {
"https://services.odata.org/Northwind/Northwind.svc/",
"https://api.mycompany.com/data/"
};
return trustedEndpoints.Any(endpoint => url.StartsWith(endpoint));
}
}GraphQL Adaptor in Blazor DataManager
The GraphQLAdaptor enables SfDataManager to interact with GraphQL services. Unlike REST adaptors, it uses queries and mutations to fetch data and perform CRUD operations. Configuration is done through GraphQLAdaptorOptions.
Table of Contents
- Overview
- Fetching Data with a Query
- CRUD Mutations
- Batch Editing
- DataManagerRequest Model
- Server-Side Setup
---
Overview
To use GraphQLAdaptor:
1. Set Adaptor="Adaptors.GraphQLAdaptor" on SfDataManager 2. Set Url to your trusted, authenticated GraphQL endpoint 3. Configure GraphQLAdaptorOptions with:
Query— the GraphQL query stringResolverName— maps the response todata.{ResolverName}Mutation(optional) — for CRUD operations
⚠️ SECURITY CRITICAL:
- Only connect to GraphQL services you control or explicitly trust
- Use HTTPS only and authenticate all requests
- Validate the GraphQL endpoint URL against a whitelist
- Sanitize all response data before rendering to UI
- Implement rate limiting and request monitoring
The GraphQL server resolver receives a DataManagerRequest input variable and must return a JSON response with count, result, and optionally aggregates.
---
Fetching Data with a Query
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<!-- SECURITY: Use string variable for GraphQL endpoint URL with whitelist validation -->
<SfGrid TValue="Order" AllowPaging="true" PageSettings="new PageSettings { PageSize = 10 }">
<SfDataManager Url="@GraphQLEndpointUrl"
GraphQLAdaptorOptions="@adaptorOptions"
Adaptor="Adaptors.GraphQLAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(Order.OrderID)" HeaderText="Order ID" Width="120"
TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field="@nameof(Order.CustomerID)" HeaderText="Customer Name" Width="150"></GridColumn>
<GridColumn Field="@nameof(Order.OrderDate)" HeaderText="Order Date"
Type="ColumnType.Date" Format="d" Width="130"></GridColumn>
<GridColumn Field="@nameof(Order.Freight)" HeaderText="Freight" Format="C2"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
// Whitelist of trusted GraphQL endpoints
private static readonly HashSet<string> TrustedGraphQLEndpoints = new()
{
"https://api.yourtrusted-domain.com/graphql"
};
// String variable for GraphQL endpoint URL
private string GraphQLEndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://api.yourtrusted-domain.com/graphql";
if (!TrustedGraphQLEndpoints.Contains(endpoint))
throw new InvalidOperationException($"Security validation failed: GraphQL endpoint '{endpoint}' is not in the trusted list");
GraphQLEndpointUrl = endpoint;
}
private GraphQLAdaptorOptions adaptorOptions = new GraphQLAdaptorOptions
{
Query = @"
query ordersData($dataManager: DataManagerRequestInput!) {
ordersData(dataManager: $dataManager) {
count,
result { OrderID, CustomerID, OrderDate, Freight },
aggregates
}
}",
ResolverName = "OrdersData"
};
public class Order
{
public int? OrderID { get; set; }
public string CustomerID { get; set; }
public DateTime? OrderDate { get; set; }
public double? Freight { get; set; }
}
}ResolverNamemust match the resolver field name in the GraphQL response underdata.{ResolverName}(case-insensitive match).
---
CRUD Mutations
Define mutations for Insert, Update, and Delete in GraphQLAdaptorOptions.Mutation. The Grid triggers these automatically when editing is enabled.
Configuration (Blazor component)
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
<!-- SECURITY: Use string variable for GraphQL endpoint URL with whitelist validation -->
<SfDataManager Url="@GraphQLCrudEndpointUrl"
GraphQLAdaptorOptions="@_adaptorOptions"
Adaptor="Adaptors.GraphQLAdaptor">
</SfDataManager>
@code {
// Whitelist of trusted GraphQL endpoints
private static readonly HashSet<string> TrustedGraphQLEndpoints = new()
{
"https://api.yourtrusted-domain.com/graphql"
};
// String variable for GraphQL endpoint URL
private string GraphQLCrudEndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://api.yourtrusted-domain.com/graphql";
if (!TrustedGraphQLEndpoints.Contains(endpoint))
throw new InvalidOperationException($"Security validation failed: GraphQL endpoint '{endpoint}' is not in the trusted list");
GraphQLCrudEndpointUrl = endpoint;
}
private GraphQLAdaptorOptions _adaptorOptions = new GraphQLAdaptorOptions
{
Query = @"
query ordersData($dataManager: DataManagerRequestInput!) {
ordersData(dataManager: $dataManager) {
count,
result { OrderID, CustomerID, OrderDate, Freight },
aggregates
}
}",
Mutation = new GraphQLMutation
{
Insert = @"
mutation create($record: OrderInput!, $index: Int!, $action: String!, $additionalParameters: Any) {
createOrder(order: $record, index: $index, action: $action, additionalParameters: $additionalParameters) {
OrderID, CustomerID, OrderDate, Freight
}
}",
Update = @"
mutation update($record: OrderInput!, $action: String!, $primaryColumnName: String!, $primaryColumnValue: Int!, $additionalParameters: Any) {
updateOrder(order: $record, action: $action, primaryColumnName: $primaryColumnName, primaryColumnValue: $primaryColumnValue, additionalParameters: $additionalParameters) {
OrderID, CustomerID, OrderDate, Freight
}
}",
Delete = @"
mutation delete($primaryColumnValue: Int!, $action: String!, $primaryColumnName: String!, $additionalParameters: Any) {
deleteOrder(primaryColumnValue: $primaryColumnValue, action: $action, primaryColumnName: $primaryColumnName, additionalParameters: $additionalParameters) {
OrderID, CustomerID, OrderDate, Freight
}
}"
},
ResolverName = "OrdersData"
};
}Mutation Parameters Reference
Insert:
| Parameter | Description |
|---|---|
record | New record to insert |
index | Position to insert at |
action | Operation type (e.g., "Add") |
additionalParameters | Optional custom data |
Update:
| Parameter | Description |
|---|---|
record | Updated record |
action | Operation type (e.g., "Edit") |
primaryColumnName | Name of the primary key field |
primaryColumnValue | Value of the primary key |
additionalParameters | Optional custom data |
Delete:
| Parameter | Description |
|---|---|
primaryColumnValue | Primary key value of record to delete |
action | Operation type (e.g., "Delete") |
primaryColumnName | Name of the primary key field |
additionalParameters | Optional custom data |
Server-Side Mutation Resolvers
public class GraphQLMutation
{
public Order CreateOrder(Order order, int index, string action,
[GraphQLType(typeof(AnyType))] IDictionary<string, object> additionalParameters)
{
var list = GraphQLQuery.Orders;
var safeIndex = Math.Clamp(index, 0, list.Count);
list.Insert(safeIndex, order);
return order;
}
public Order UpdateOrder(Order order, string action, string primaryColumnName,
int primaryColumnValue,
[GraphQLType(typeof(AnyType))] IDictionary<string, object> additionalParameters)
{
var existing = GraphQLQuery.Orders.FirstOrDefault(x => x.OrderID == primaryColumnValue);
if (existing == null) return order;
existing.OrderID = order.OrderID;
existing.CustomerID = order.CustomerID;
existing.Freight = order.Freight;
existing.OrderDate = order.OrderDate;
return existing;
}
public Order DeleteOrder(int primaryColumnValue, string action, string primaryColumnName,
[GraphQLType(typeof(AnyType))] IDictionary<string, object> additionalParameters)
{
var target = GraphQLQuery.Orders.FirstOrDefault(x => x.OrderID == primaryColumnValue);
if (target != null) GraphQLQuery.Orders.Remove(target);
return target;
}
}---
Batch Editing
Batch editing sends all pending Insert, Update, and Delete operations in a single GraphQL request. Configure via Mutation.Batch.
@code {
private GraphQLAdaptorOptions _adaptorOptions = new GraphQLAdaptorOptions
{
Query = @"
query ordersData($dataManager: DataManagerRequestInput!) {
ordersData(dataManager: $dataManager) {
count,
result { OrderID, CustomerID, OrderDate, Freight },
aggregates
}
}",
Mutation = new GraphQLMutation
{
Batch = @"
mutation batch(
$changed: [OrderInput!], $added: [OrderInput!], $deleted: [OrderInput!],
$action: String!, $primaryColumnName: String!,
$additionalParameters: Any, $dropIndex: Int
) {
batchUpdate(
changed: $changed, added: $added, deleted: $deleted,
action: $action, primaryColumnName: $primaryColumnName,
additionalParameters: $additionalParameters, dropIndex: $dropIndex
) { OrderID, CustomerID, OrderDate, Freight }
}"
},
ResolverName = "OrdersData"
};
}Batch mutation parameters:
| Parameter | Description |
|---|---|
changed | Records to update |
added | Records to insert |
deleted | Records to remove |
action | Operation type |
primaryColumnName | Primary key field name |
additionalParameters | Optional custom data |
dropIndex | Insert position for drag-and-drop |
Server-Side Batch Resolver
public class GraphQLMutation
{
public List<Order> BatchUpdate(
List<Order>? changed,
List<Order>? added,
List<Order>? deleted,
string action,
string primaryColumnName,
[GraphQLType(typeof(AnyType))] IDictionary<string, object>? additionalParameters,
int? dropIndex)
{
// Update existing records
if (changed != null && changed.Count > 0)
{
foreach (var changedOrder in changed)
{
var target = GraphQLQuery.Orders.FirstOrDefault(e => e.OrderID == changedOrder.OrderID);
if (target != null)
{
target.CustomerID = changedOrder.CustomerID;
target.OrderDate = changedOrder.OrderDate;
target.Freight = changedOrder.Freight;
}
}
}
// Insert new records — respect drag-and-drop index if provided
if (added != null && added.Count > 0)
{
if (dropIndex.HasValue)
{
var index = Math.Clamp(dropIndex.Value, 0, GraphQLQuery.Orders.Count);
GraphQLQuery.Orders.InsertRange(index, added);
}
else
{
GraphQLQuery.Orders.AddRange(added);
}
}
// Delete records
if (deleted != null && deleted.Count > 0)
{
foreach (var deletedOrder in deleted)
{
var target = GraphQLQuery.Orders.FirstOrDefault(e => e.OrderID == deletedOrder.OrderID);
if (target != null)
GraphQLQuery.Orders.Remove(target);
}
}
return GraphQLQuery.Orders;
}
}Key points:
InsertRangewith a clampeddropIndexhandles drag-and-drop row reordering safely- Primary key fields (
OrderID) should not be overwritten during updates - The method returns the full updated collection — the Grid reconciles the state client-side
---
DataManagerRequest Model
The GraphQL server resolver receives a DataManagerRequestInput variable containing all query parameters from the DataManager. Use this full model — including all [GraphQLName] annotations and supporting classes — when implementing a server-side resolver:
public class DataManagerRequest
{
[GraphQLName("Skip")]
public int Skip { get; set; }
[GraphQLName("Take")]
public int Take { get; set; }
[GraphQLName("RequiresCounts")]
public bool RequiresCounts { get; set; } = false;
[GraphQLName("Params")]
[GraphQLType(typeof(AnyType))]
public IDictionary<string, object> Params { get; set; }
[GraphQLName("Aggregates")]
[GraphQLType(typeof(AnyType))]
public List<Aggregate>? Aggregates { get; set; }
[GraphQLName("Search")]
public List<SearchFilter>? Search { get; set; }
[GraphQLName("Sorted")]
public List<Sort>? Sorted { get; set; }
[GraphQLName("Where")]
[GraphQLType(typeof(AnyType))]
public List<WhereFilter>? Where { get; set; }
[GraphQLName("Group")]
public List<string>? Group { get; set; }
[GraphQLName("antiForgery")]
public string? antiForgery { get; set; }
[GraphQLName("Table")]
public string? Table { get; set; }
[GraphQLName("IdMapping")]
public string? IdMapping { get; set; }
[GraphQLName("Select")]
public List<string>? Select { get; set; }
[GraphQLName("Expand")]
public List<string>? Expand { get; set; }
[GraphQLName("Distinct")]
public List<string>? Distinct { get; set; }
[GraphQLName("ServerSideGroup")]
public bool? ServerSideGroup { get; set; }
[GraphQLName("LazyLoad")]
public bool? LazyLoad { get; set; }
[GraphQLName("LazyExpandAllGroup")]
public bool? LazyExpandAllGroup { get; set; }
}
public class Aggregate
{
[GraphQLName("Field")]
public string Field { get; set; }
[GraphQLName("Type")]
public string Type { get; set; }
}
public class SearchFilter
{
[GraphQLName("Fields")]
public List<string> Fields { get; set; }
[GraphQLName("Key")]
public string Key { get; set; }
[GraphQLName("Operator")]
public string Operator { get; set; }
[GraphQLName("IgnoreCase")]
public bool IgnoreCase { get; set; }
}
public class Sort
{
[GraphQLName("Name")]
public string Name { get; set; }
[GraphQLName("Direction")]
public string Direction { get; set; }
[GraphQLName("Comparer")]
[GraphQLType(typeof(AnyType))]
public object Comparer { get; set; }
}
public class WhereFilter
{
[GraphQLName("Field")]
public string? Field { get; set; }
[GraphQLName("IgnoreCase")]
public bool? IgnoreCase { get; set; }
[GraphQLName("IgnoreAccent")]
public bool? IgnoreAccent { get; set; }
[GraphQLName("IsComplex")]
public bool? IsComplex { get; set; }
[GraphQLName("Operator")]
public string? Operator { get; set; }
[GraphQLName("Condition")]
public string? Condition { get; set; }
[GraphQLName("value")]
[GraphQLType(typeof(AnyType))]
public object? value { get; set; }
[GraphQLName("predicates")]
public List<WhereFilter>? predicates { get; set; }
}The resolver uses these parameters to apply searching, sorting, filtering, and paging before returning the response.
---
Server-Side Setup
GraphQL Query Resolver
Implement a GraphQLQuery class that consumes DataManagerRequest and applies data operations using the DataOperations helper. Return a ReturnType<T> response that includes Result, Count, and optionally Aggregates.
public class GraphQLQuery
{
public ReturnType<Order> OrdersData(DataManagerRequest dataManager)
{
IEnumerable<Order> result = Orders;
if (dataManager.Search != null && dataManager.Search.Count > 0)
result = DataOperations.PerformSearching(result, dataManager.Search);
if (dataManager.Sorted != null && dataManager.Sorted.Count > 0)
result = DataOperations.PerformSorting(result, dataManager.Sorted);
if (dataManager.Where != null && dataManager.Where.Count > 0)
result = DataOperations.PerformFiltering(result, dataManager.Where, dataManager.Where[0].Operator);
int count = result.Count();
if (dataManager.Skip != 0)
result = DataOperations.PerformSkip(result, dataManager.Skip);
if (dataManager.Take != 0)
result = DataOperations.PerformTake(result, dataManager.Take);
if (dataManager.Aggregates != null)
{
IDictionary<string, object> aggregates = DataUtil.PerformAggregation(result, dataManager.Aggregates);
return new ReturnType<Order>() { Count = count, Result = result, Aggregates = aggregates };
}
return dataManager.RequiresCounts
? new ReturnType<Order>() { Result = result, Count = count }
: new ReturnType<Order>() { Result = result };
}
public static List<Order> Orders { get; set; } = Enumerable.Range(1, 75).Select(x => new Order
{
OrderID = 1000 + x,
CustomerID = new[] { "ALFKI", "ANANTR", "ANTON", "BLONP", "BOLID" }[x % 5],
OrderDate = new DateTime(2023, 1, 1).AddDays(x),
Freight = Math.Round(2.1 * x, 2)
}).ToList();
}
// Response wrapper returned by all GraphQL query resolvers
public class ReturnType<T>
{
public int Count { get; set; }
public IEnumerable<T> Result { get; set; }
[GraphQLType(typeof(AnyType))]
public IDictionary<string, object> Aggregates { get; set; }
}ResolverNameinGraphQLAdaptorOptionsmust match the method name (case-insensitive). For the resolverOrdersData(...)above, setResolverName = "OrdersData".
Register Schema and Configure CORS in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGraphQLServer()
.AddQueryType<GraphQLQuery>()
.AddMutationType<GraphQLMutation>();
// SECURITY: Restrict CORS to only the specific Blazor app origin
// Never use wildcard (*) in production
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin", cors =>
{
cors.WithOrigins("https://your-blazor-app-url") // Use HTTPS only
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
var app = builder.Build();
app.UseCors("AllowSpecificOrigin");
app.MapGraphQL();How-To Guides for Blazor DataManager
Focused guides for common DataManager configuration scenarios.
Table of Contents
---
Adding Custom HTTP Headers
Use the Headers property to attach custom key-value pairs to every outbound HTTP request made by SfDataManager. This is the standard approach for passing authentication tokens, tenant identifiers, or localization context without modifying the request payload.
When to use:
- Sending Bearer tokens or API keys for authenticated endpoints
- Including a tenant identifier in multi-tenant applications
- Adding localization or versioning headers required by the server
Key points:
- Works with all built-in remote adaptors:
WebApiAdaptor,ODataAdaptor,UrlAdaptor, etc. - Headers are included automatically every time the DataManager connects to any bound component (
SfGrid,SfChart,SfListView, etc.) - Update headers at runtime (e.g., on token refresh) — the property is reactive
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<!-- SECURITY: Use string variable for endpoint URL with whitelist validation -->
<SfGrid TValue="Order" AllowPaging="true">
<GridPageSettings PageSize="10"></GridPageSettings>
<SfDataManager Headers="@HeaderData"
Url="@AuthenticatedWebApiEndpointUrl"
Adaptor="Adaptors.WebApiAdaptor">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(Order.OrderID)" HeaderText="Order ID" IsPrimaryKey="true"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
<GridColumn Field="@nameof(Order.CustomerID)" HeaderText="Customer Name" Width="150"></GridColumn>
<GridColumn Field="@nameof(Order.OrderDate)" HeaderText="Order Date" Format="d"
Type="ColumnType.Date" TextAlign="TextAlign.Right" Width="130"></GridColumn>
<GridColumn Field="@nameof(Order.Freight)" HeaderText="Freight" Format="C2"
TextAlign="TextAlign.Right" Width="120"></GridColumn>
</GridColumns>
</SfGrid>
@code {
// Whitelist of trusted Web API endpoints
private static readonly HashSet<string> TrustedWebApiEndpoints = new()
{
"https://blazor.syncfusion.com/services/production/api/",
"https://api.yourtrusted-domain.com/api/"
};
// String variable for endpoint URL
private string AuthenticatedWebApiEndpointUrl { get; set; } = string.Empty;
// Authentication headers
private IDictionary<string, string> HeaderData { get; set; } = new Dictionary<string, string>();
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://blazor.syncfusion.com/services/production/api/Orders/";
if (!TrustedWebApiEndpoints.Any(trusted => endpoint.StartsWith(trusted)))
throw new InvalidOperationException($"Security validation failed: endpoint '{endpoint}' is not in the trusted list");
AuthenticatedWebApiEndpointUrl = endpoint;
// Configure headers with authentication token (retrieve from secure storage in production)
HeaderData = new Dictionary<string, string>
{
{ "Authorization", "Bearer <token>" }, // Replace with actual token from secure storage
{ "X-Tenant-ID", "Tenant123" }
};
}
public class Order
{
public int? OrderID { get; set; }
public string CustomerID { get; set; }
public DateTime? OrderDate { get; set; }
public double? Freight { get; set; }
}
}Note: Keep sensitive data such as tokens in headers rather than in the URL or request body — this reduces payload size and improves security.
---
Enabling Offline Mode
Set Offline="true" on SfDataManager to fetch the complete dataset once from the remote service and then execute all subsequent operations (filtering, sorting, paging, grouping) client-side without additional network requests.
When to use:
- You have a remote service but the dataset is reasonably sized and doesn't change frequently
- You want to reduce network traffic while still sourcing from a remote endpoint
- You need operations to continue working if connectivity becomes intermittent after the initial load
- You're testing or demoing and want to avoid repeated API calls
Key points:
- On first render,
SfDataManagerfetches the complete collection from the remote endpoint — ensure the server returns all records (not just one page) whenOfflineis enabled - All filtering, sorting, paging, and grouping then run in the browser against the cached
Jsonproperty - Compatible with
ODataAdaptor,ODataV4Adaptor, andWebApiAdaptor
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.Grids
<!-- SECURITY: Use string variable for endpoint URL with whitelist validation -->
<SfGrid TValue="EmployeeData" ID="Grid" AllowPaging="true">
<SfDataManager Url="@OfflineODataEndpointUrl"
Adaptor="Adaptors.ODataAdaptor"
Offline="true">
</SfDataManager>
<GridColumns>
<GridColumn Field="@nameof(EmployeeData.OrderID)" HeaderText="Order ID"
Width="120" TextAlign="TextAlign.Center" />
<GridColumn Field="@nameof(EmployeeData.CustomerID)" HeaderText="Customer Name"
Width="130" TextAlign="TextAlign.Center" />
<GridColumn Field="@nameof(EmployeeData.EmployeeID)" HeaderText="Employee ID"
Width="120" TextAlign="TextAlign.Center" />
</GridColumns>
</SfGrid>
@code {
// Whitelist of trusted OData endpoints
private static readonly HashSet<string> TrustedODataEndpoints = new()
{
"https://services.odata.org/Northwind/Northwind.svc/",
"https://api.yourtrusted-domain.com/odata/"
};
// String variable for endpoint URL
private string OfflineODataEndpointUrl { get; set; } = string.Empty;
protected override void OnInitialized()
{
// Define endpoint and validate against whitelist
const string endpoint = "https://services.odata.org/Northwind/Northwind.svc/Orders";
if (!TrustedODataEndpoints.Any(trusted => endpoint.StartsWith(trusted)))
throw new InvalidOperationException($"Security validation failed: endpoint '{endpoint}' is not in the trusted list");
OfflineODataEndpointUrl = endpoint;
}
public class EmployeeData
{
public int OrderID { get; set; }
public string CustomerID { get; set; }
public int EmployeeID { get; set; }
}
}Gotcha: If the remote endpoint returns paginated results by default, ensure the server-side paging is disabled or the page size is set to "all records" when Offline="true" — otherwise only the first page will be cached and available for client-side operations.