The DotCmsService is the core service class responsible for interacting with the dotCMS API. It provides methods to fetch pages, execute GraphQL queries, and retrieve navigation data from dotCMS instances. The service implements caching, authentication, and error handling to ensure reliable and performant communication with the dotCMS backend.
- Class Overview
- Constructor
- Configuration
- Public Methods
- Caching Strategy
- Authentication
- Error Handling
- Security Features
- Usage Examples
- Performance Considerations
public class DotCmsService : IDotCmsServiceThe DotCmsService class implements the IDotCmsService interface and provides the following key features:
- Page API Integration: Fetch pages using dotCMS Page API
- GraphQL Support: Execute GraphQL queries against dotCMS
- Navigation API: Retrieve site navigation structure
- Caching: Built-in caching with configurable TTL
- Authentication: Support for both API tokens and basic authentication
- Error Handling: Comprehensive logging and exception handling
- Security: Path traversal protection and GraphQL injection prevention
public DotCmsService(
HttpClient httpClient,
IConfiguration configuration,
ILogger<DotCmsService> logger,
IAppCache cache,
ModelHelper modelHelper)httpClient: HTTP client for making API requestsconfiguration: Application configuration containing dotCMS settingslogger: Logger instance for diagnostic informationcache: LazyCache instance for response cachingmodelHelper: Helper for converting GraphQL responses to page models
ArgumentNullException: Thrown when any required dependency is nullInvalidOperationException: Thrown when required configuration values are missing
The service requires the ApiHost and either an ApiToken OR an ApiUsername/Password in the appsettings.json/
{
"dotCMS": {
"ApiHost": "https://your-dotcms-instance.com",
"ApiToken": "your-api-token",
"ApiUserName": "fallback-username",
"ApiPassword": "fallback-password",
"CacheTTL": 120
}
}| Key | Required | Description | Default |
|---|---|---|---|
dotCMS:ApiHost |
Yes | dotCMS instance URL | - |
dotCMS:ApiToken |
Recommended | API token for authentication | - |
dotCMS:ApiUserName |
If no token | Username for basic auth | - |
dotCMS:ApiPassword |
If no token | Password for basic auth | - |
dotCMS:CacheTTL |
No | Default cache TTL in seconds | 120 |
Retrieves a page from dotCMS using the Page API.
public async Task<PageResponse> GetPageAsync(PageQueryParams queryParams)queryParams: Query parameters containing path, site, mode, language, persona, etc.
PageResponse: Complete page data including layout, containers, and contentlets
var queryParams = new PageQueryParams
{
Path = "/about-us",
Site = "demo.dotcms.com",
PageMode = "LIVE_MODE",
Language = "1",
CacheSeconds = 300
};
var pageResponse = await dotCmsService.GetPageAsync(queryParams);Retrieves a page from dotCMS using GraphQL.
public async Task<PageResponse> GetPageGraphqlAsync(PageQueryParams queryParams)queryParams: Query parameters for the GraphQL request
PageResponse: Page data converted from GraphQL response
var queryParams = new PageQueryParams
{
Path = "/products",
Site = "demo.dotcms.com",
PageMode = "PREVIEW_MODE"
};
var pageResponse = await dotCmsService.GetPageGraphqlAsync(queryParams);Executes a custom GraphQL query against dotCMS.
public async Task<string> QueryGraphqlAsync(string graphqlQuery)
public async Task<string> QueryGraphqlAsync(string graphqlQuery, int cacheSeconds)graphqlQuery: The GraphQL query stringcacheSeconds: Optional cache duration (0 = no cache)
string: Raw GraphQL response content
var query = @"
{
contentlets(query: "+contentType:Product", limit: 10) {
title
identifier
_map
}
}";
var response = await dotCmsService.QueryGraphqlAsync(query, 60);Retrieves the site navigation structure.
public async Task<NavigationResponse> GetNavigationAsync(int depth = 4)depth: Navigation hierarchy depth (0-10, default: 4)
NavigationResponse: Site navigation structure
var navigation = await dotCmsService.GetNavigationAsync(3);The service implements intelligent caching with different TTL values for different scenarios:
| Scenario | Default TTL | Configurable |
|---|---|---|
| Live Mode Pages | 120 seconds | Yes (dotCMS:CacheTTL) |
| Edit/Preview Mode | 0 seconds | No |
| Navigation | 60 seconds | No |
| GraphQL Queries | 60 seconds | Via parameter |
- Page API: Full request URL including query parameters
- GraphQL: SHA256 hash of the query string
- Navigation: Request URL with depth parameter
// Live mode - cached
var liveParams = new PageQueryParams { Path = "/home", PageMode = "LIVE_MODE" };
var cachedResponse = await service.GetPageAsync(liveParams); // Cached for 120s
// Edit mode - not cached
var editParams = new PageQueryParams { Path = "/home", PageMode = "EDIT_MODE" };
var freshResponse = await service.GetPageAsync(editParams); // Always fresh
// Custom cache duration
var customParams = new PageQueryParams {
Path = "/home",
PageMode = "LIVE_MODE",
CacheSeconds = 300
};
var customCachedResponse = await service.GetPageAsync(customParams); // Cached for 300sThe service supports two authentication methods:
{
"dotCMS": {
"ApiToken": "your-api-token-here"
}
}Uses Bearer token authentication:
Authorization: Bearer your-api-token-here
{
"dotCMS": {
"ApiUserName": "admin@dotcms.com",
"ApiPassword": "admin"
}
}Uses Basic authentication with base64-encoded credentials:
Authorization: Basic YWRtaW5AZG90Y21zLmNvbTphZG1pbg==
Note: Basic authentication forces a login on every request and is less performant than API tokens.
The service implements comprehensive error handling:
ArgumentNullException: Invalid method parametersArgumentException: Invalid path or query parametersHttpRequestException: API communication errorsJsonException: Response deserialization errorsInvalidOperationException: Configuration errors
The service logs important events at different levels:
// Information
_logger.LogInformation("Requesting page from: {RequestUrl}", requestUrl);
// Warning
_logger.LogWarning("Navigation depth {Depth} exceeds recommended maximum", depth);
// Error
_logger.LogError(ex, "Error in GetPageAsync for path: {Path}", queryParams.Path);private async Task EnsureSuccessStatusCode(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogWarning("API returned status code: {StatusCode}, Content: {Content}",
response.StatusCode, errorContent);
throw new HttpRequestException($"API returned status code: {response.StatusCode}");
}
}The service validates paths to prevent directory traversal attacks:
private static string NormalizePath(string? path)
{
// Security: Prevent path traversal attacks
if (path.Contains("..") || path.Contains("~") || path.Contains("\\"))
{
throw new ArgumentException("Invalid path: Path traversal patterns are not allowed");
}
// Additional normalization...
}GraphQL strings are properly escaped to prevent injection attacks:
private static string EscapeGraphqlString(string input)
{
return input
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\n", "\\n")
.Replace("\r", "\\r")
.Replace("\t", "\\t");
}All public methods validate their parameters:
public async Task<PageResponse> GetPageAsync(PageQueryParams queryParams)
{
ArgumentNullException.ThrowIfNull(queryParams);
// Method implementation...
}public class HomeController : Controller
{
private readonly IDotCmsService _dotCmsService;
public HomeController(IDotCmsService dotCmsService)
{
_dotCmsService = dotCmsService;
}
public async Task<IActionResult> Index()
{
var queryParams = new PageQueryParams
{
Path = "/",
PageMode = "LIVE_MODE"
};
var pageResponse = await _dotCmsService.GetPageAsync(queryParams);
return View(pageResponse);
}
}public async Task<IActionResult> GetLocalizedPage(string path, string language)
{
var queryParams = new PageQueryParams
{
Path = path,
Language = language,
PageMode = "LIVE_MODE",
CacheSeconds = 300
};
var pageResponse = await _dotCmsService.GetPageAsync(queryParams);
return View(pageResponse);
}public async Task<IActionResult> GetProducts()
{
var query = @"
{
contentlets(contentType: ""Product"", limit: 20, sortBy: ""modDate desc"") {
title
identifier
modDate
_map
}
}";
var response = await _dotCmsService.QueryGraphqlAsync(query, 120);
var products = JsonSerializer.Deserialize<ProductResponse>(response);
return View(products);
}public async Task<IActionResult> GetNavigation()
{
try
{
var navigation = await _dotCmsService.GetNavigationAsync(3);
return PartialView("_Navigation", navigation);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Failed to load navigation");
return PartialView("_NavigationError");
}
}-
Use appropriate cache durations:
- Live content: 2-5 minutes
- Navigation: 1-2 minutes
- Static content: 10-30 minutes
-
Disable caching for edit modes:
var editParams = new PageQueryParams { Path = path, PageMode = "EDIT_MODE", CacheSeconds = 0 // No caching for edit mode };
-
Monitor cache hit rates and adjust TTL values based on content update frequency.
Configure the HTTP client for optimal performance:
services.AddHttpClient<DotCmsService>(client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.Add("User-Agent", "DotCMS-.NET-SDK/1.0");
});The service properly disposes of HTTP resources:
using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
using var response = await _httpClient.SendAsync(request);The service requires the following NuGet packages:
Microsoft.Extensions.Http- HTTP client factoryMicrosoft.Extensions.Configuration- Configuration managementMicrosoft.Extensions.Logging- Logging infrastructureLazyCache- In-memory cachingSystem.Text.Json- JSON serialization
The DotCmsService is thread-safe and can be registered as a singleton in the DI container:
services.AddSingleton<IDotCmsService, DotCmsService>();All shared state is immutable, and the LazyCache implementation is thread-safe.