SitecoreAPIGraphQLClient 1.0.0
dotnet add package SitecoreAPIGraphQLClient --version 1.0.0
NuGet\Install-Package SitecoreAPIGraphQLClient -Version 1.0.0
<PackageReference Include="SitecoreAPIGraphQLClient" Version="1.0.0" />
<PackageVersion Include="SitecoreAPIGraphQLClient" Version="1.0.0" />
<PackageReference Include="SitecoreAPIGraphQLClient" />
paket add SitecoreAPIGraphQLClient --version 1.0.0
#r "nuget: SitecoreAPIGraphQLClient, 1.0.0"
#:package SitecoreAPIGraphQLClient@1.0.0
#addin nuget:?package=SitecoreAPIGraphQLClient&version=1.0.0
#tool nuget:?package=SitecoreAPIGraphQLClient&version=1.0.0
SitecoreAPIGraphQLClient
A high-performance, thread-safe .NET 8, .NET 9, and .NET 10 GraphQL client factory and HTTP authentication pipeline for Sitecore XM Cloud, Experience Edge, and Authoring/Management APIs. Built on SitecoreAPIAuthorization 1.0.0.
Key Features
- Multi-targeted for
.NET 8.0,.NET 9.0, and.NET 10.0with portable.snupkgsymbols and SourceLink - Strict per-client
(url, clientId, clientSecret)OAuth2 bearer-token isolation across named and dynamic multi-tenant clients - Preserves 2-minute
IHttpClientFactorysocket handler rotation for DNS updates while cachingIGraphQLClientinstances (MaxCachedClientsFIFO eviction for dynamic tenants) - Automatic
HTTP 401 Unauthorizedrecovery with single-flight token refresh coalescing (TryRefreshSitecoreAuthToken) andHTTP POSTGraphQL mutation payload replay - Opt-in HTTP resilience (
Microsoft.Extensions.Http.Resilience) partitioned per(authority, client)withHTTP POSTretries disabled by default (AllowPostRetries = false) and configurable per read-only client - Secure by default: enforces
https://(or loopbackhttp://) unlessAllowInsecureHttp = true, rejects embeddedUri.UserInfo, redacts query strings (?sc_apikey=...) from logs/exceptions, and masks secrets inToString() - Compatible with both
ScopedandSingletonISitecoreTokenServicelifetimes (ValidateScopes = true,ValidateOnBuild = true) - Built-in
System.Diagnostics(ActivitySourceandMeter) telemetry and source-generated[LoggerMessage]logging
Installation
dotnet add package SitecoreAPIAuthorization
dotnet add package SitecoreAPIGraphQLClient
Configuration (appsettings.json)
{
"SitecoreAuthentication": {
"AuthTokenUrl": "https://auth.sitecorecloud.io/oauth/token"
},
"Sitecore": {
"GraphQL": {
"Endpoint": "https://edge.sitecorecloud.io/api/graphql/v1?sc_apikey=your-api-key",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"EnableUnauthorizedRefresh": true,
"MaxUnauthorizedRetries": 1,
"AllowInsecureHttp": false,
"AllowPostRetries": false,
"MaxCachedClients": 128,
"Clients": {
"authoring": {
"Endpoint": "https://xmcloud.example/sitecore/api/authoring/graphql/v1",
"ClientId": "authoring-client-id",
"ClientSecret": "authoring-client-secret"
},
"delivery": {
"Endpoint": "https://edge.sitecorecloud.io/api/graphql/v1?sc_apikey=delivery-key",
"ClientId": "delivery-client-id",
"ClientSecret": "delivery-client-secret",
"AllowPostRetries": true
}
}
}
}
}
Registration
using Sitecore.API.Foundation.Authorization.DependencyInjection;
using Sitecore.API.Foundation.GraphQL.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// 1. Register Sitecore OAuth2 token service (Scoped by default)
builder.Services.AddSitecoreAuthentication(builder.Configuration);
// 2. Register Sitecore GraphQL client factory (with optional HTTP resilience)
builder.Services.AddSitecoreGraphQL(
builder.Configuration,
configureResilience: resilience =>
{
resilience.Retry.MaxRetryAttempts = 2;
resilience.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);
});
Delegate-based registration without IConfiguration is also supported:
builder.Services.AddSitecoreGraphQL(options =>
{
options.Endpoint = "https://edge.sitecorecloud.io/api/graphql/v1";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
});
Basic Usage
GraphQLHttpClient uses NewtonsoftJsonSerializer (Newtonsoft.Json). Deserialize responses into strongly typed DTOs or Newtonsoft.Json.Linq.JObject:
using GraphQL;
using Newtonsoft.Json;
using Sitecore.API.Foundation.GraphQL;
public sealed class PageQueryResponse
{
[JsonProperty("item")]
public PageItem? Item { get; set; }
}
public sealed class PageItem
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
}
public sealed class ContentConsumer
{
private readonly ISitecoreGraphQLFactory _factory;
public ContentConsumer(ISitecoreGraphQLFactory factory) => _factory = factory;
public async Task<PageItem?> GetHomeItemAsync(CancellationToken ct = default)
{
// Do not manually dispose shared IGraphQLClient instances returned by the factory
var client = await _factory.CreateClientAsync(ct);
var request = new GraphQLRequest
{
Query = "query GetItem($path: String!, $language: String!) { item(path: $path, language: $language) { id name } }",
Variables = new { path = "/sitecore/content/Home", language = "en" }
};
var response = await client.SendQueryAsync<PageQueryResponse>(request, ct);
return response.Data?.Item;
}
}
Named, Explicit, and Manual Refresh APIs
// Named clients (any unset property inherits from top-level Sitecore:GraphQL options)
var authoringClient = await factory.CreateClientByNameAsync("authoring", ct);
var deliveryClient = await factory.CreateClientByNameAsync("delivery", ct);
// Explicit endpoint + explicit credentials (isolated per URL + ClientId + ClientSecret)
var tenantClient = await factory.CreateClientAsync(
url: "https://edge.sitecorecloud.io/api/graphql/v1?sc_apikey=tenant-key",
clientId: "tenant-client-id",
clientSecret: "tenant-client-secret",
cancellationToken: ct);
// Trusted custom endpoint with configured default credentials (never pass untrusted user URLs)
var customEndpointClient = await factory.CreateClientAsync("https://trusted.sitecore/graphql", ct);
// Manual token refresh across all configured credentials or for a single named client
bool allRefreshed = await factory.RefreshTokenAsync(ct);
bool deliveryRefreshed = await factory.RefreshTokenByNameAsync("delivery", ct);
Benchmarks
Measured with BenchmarkDotNet v0.15.8 against a 64-client populated SitecoreGraphQLFactory and in-memory HTTP transport handlers:
BenchmarkDotNet v0.15.8, macOS 27.0.1 (26A434) [Darwin 27.0.0]
Apple M1 Max, 1 CPU, 10 logical and 10 physical cores
.NET SDK 10.0.401
.NET 8.0 : .NET 8.0.25 (8.0.2526.11203), Arm64 RyuJIT armv8.0-a
.NET 9.0 : .NET 9.0.14 (9.0.1426.11910), Arm64 RyuJIT armv8.0-a
.NET 10.0 : .NET 10.0.12 (10.0.1226.42308), Arm64 RyuJIT armv8.0-a
| Method | Runtime | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|---|---|---|---|---|---|---|---|
CreateClientAsync (Explicit Cache Hit) |
.NET 8.0 | 184.9 ns | 0.53 ns | 0.49 ns | 0.0114 | - | 72 B |
CreateClientAsync (Explicit Cache Hit) |
.NET 9.0 | 184.1 ns | 0.33 ns | 0.31 ns | 0.0114 | - | 72 B |
CreateClientAsync (Explicit Cache Hit) |
.NET 10.0 | 173.5 ns | 0.55 ns | 0.49 ns | 0.0114 | - | 72 B |
CreateClientByNameAsync (Named Cache Hit) |
.NET 8.0 | 201.1 ns | 0.62 ns | 0.55 ns | 0.0114 | - | 72 B |
CreateClientByNameAsync (Named Cache Hit) |
.NET 9.0 | 231.3 ns | 0.62 ns | 0.58 ns | 0.0114 | - | 72 B |
CreateClientByNameAsync (Named Cache Hit) |
.NET 10.0 | 196.7 ns | 0.46 ns | 0.43 ns | 0.0114 | - | 72 B |
SitecoreTokenHandler.SendAsync (200 OK POST) |
.NET 8.0 | 666.7 ns | 2.30 ns | 2.16 ns | 0.3824 | 0.0019 | 2,400 B |
SitecoreTokenHandler.SendAsync (200 OK POST) |
.NET 9.0 | 617.7 ns | 2.27 ns | 2.13 ns | 0.3614 | 0.0010 | 2,272 B |
SitecoreTokenHandler.SendAsync (200 OK POST) |
.NET 10.0 | 533.4 ns | 1.71 ns | 1.34 ns | 0.3595 | 0.0010 | 2,256 B |
SitecoreTokenHandler.SendAsync (401 Refresh + POST Replay) |
.NET 8.0 | 1,724.6 ns | 4.20 ns | 3.93 ns | 0.8793 | 0.0057 | 5,520 B |
SitecoreTokenHandler.SendAsync (401 Refresh + POST Replay) |
.NET 9.0 | 1,771.4 ns | 5.64 ns | 5.00 ns | 0.8888 | 0.0076 | 5,584 B |
SitecoreTokenHandler.SendAsync (401 Refresh + POST Replay) |
.NET 10.0 | 1,562.0 ns | 6.04 ns | 5.04 ns | 0.8774 | 0.0076 | 5,504 B |
Run the benchmark suite locally:
dotnet run --configuration Release --framework net10.0 \
--project tests/Sitecore.API.Foundation.GraphQL.Benchmarks/Sitecore.API.Foundation.GraphQL.Benchmarks.csproj
Options Summary
| Option | Default | Description |
|---|---|---|
Endpoint |
null |
Default GraphQL endpoint URL (https:// or loopback http:// by default). |
ClientId |
null |
Default OAuth2 client identifier. |
ClientSecret |
null |
Default OAuth2 client secret (masked as *** in ToString()). |
EnableUnauthorizedRefresh |
true |
Refresh token and replay request on 401 Unauthorized. |
MaxUnauthorizedRetries |
1 |
Maximum retry attempts after an initial 401 (0 disables retries). |
EnableInternalLoggingSetup |
true |
Calls AddLogging() if the host has not configured logging. |
AllowInsecureHttp |
false |
Permits non-loopback http:// endpoints (for internal container networks). |
AllowPostRetries |
false |
Permits transient resilience retries on HTTP POST requests (overridable per named client). |
MaxCachedClients |
128 |
Maximum dynamic clients cached before FIFO eviction (configured clients are exempt). |
MaxRetryBufferBytes |
10485760 (10 MB) |
Maximum request content size pre-buffered for 401 / resilience POST replay. |
Clients |
{} |
Dictionary of named SitecoreGraphQLClientOptions entries inheriting unset defaults. |
Documentation
- Extended Developer and Architecture Guide — Multi-tenant patterns,
AllowInsecureHttp, HTTP resilience &AllowPostRetries,IGraphQLClientrotation/disposal, WebSocket scope,System.Diagnosticstelemetry, andv0.9.xmigration notes. - Contributing Guide — Contributor prerequisites,
.slnxlayout, coverage/pack gates, and OIDC release workflow. - Changelog — Release history.
License
MIT License — see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- GraphQL.Client (>= 6.1.0)
- GraphQL.Client.Abstractions (>= 6.1.0)
- GraphQL.Client.Serializer.Newtonsoft (>= 6.1.0)
- Microsoft.Extensions.Http (>= 10.0.14)
- Microsoft.Extensions.Http.Resilience (>= 10.11.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Options (>= 10.0.14)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.14)
- SitecoreAPIAuthorization (>= 1.0.0)
-
net8.0
- GraphQL.Client (>= 6.1.0)
- GraphQL.Client.Abstractions (>= 6.1.0)
- GraphQL.Client.Serializer.Newtonsoft (>= 6.1.0)
- Microsoft.Extensions.Http (>= 10.0.14)
- Microsoft.Extensions.Http.Resilience (>= 10.11.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Options (>= 10.0.14)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.14)
- SitecoreAPIAuthorization (>= 1.0.0)
-
net9.0
- GraphQL.Client (>= 6.1.0)
- GraphQL.Client.Abstractions (>= 6.1.0)
- GraphQL.Client.Serializer.Newtonsoft (>= 6.1.0)
- Microsoft.Extensions.Http (>= 10.0.14)
- Microsoft.Extensions.Http.Resilience (>= 10.11.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Options (>= 10.0.14)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.14)
- SitecoreAPIAuthorization (>= 1.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
See CHANGELOG.md for release notes.