XperienceCommunity.DataContext
1.0.3
dotnet add package XperienceCommunity.DataContext --version 1.0.3
NuGet\Install-Package XperienceCommunity.DataContext -Version 1.0.3
<PackageReference Include="XperienceCommunity.DataContext" Version="1.0.3" />
<PackageVersion Include="XperienceCommunity.DataContext" Version="1.0.3" />
<PackageReference Include="XperienceCommunity.DataContext" />
paket add XperienceCommunity.DataContext --version 1.0.3
#r "nuget: XperienceCommunity.DataContext, 1.0.3"
#:package XperienceCommunity.DataContext@1.0.3
#addin nuget:?package=XperienceCommunity.DataContext&version=1.0.3
#tool nuget:?package=XperienceCommunity.DataContext&version=1.0.3
XperienceCommunity.DataContext
Enhance your Kentico Xperience development with a fluent API for intuitive and efficient query building. This project abstracts the built-in ContentItemQueryBuilder, leveraging .NET 6/.NET 8/.NET 9 and integrated with Xperience By Kentico, to improve your local development and testing workflow.
Features
- Fluent API for query building with strongly-typed expressions
- Built-in caching with automatic cache dependency management
- Three specialized contexts for different content types:
IContentItemContext<T>
- For content hub items with strongly-typed queryingIPageContentContext<T>
- For web pages with channel and path filtering capabilitiesIReusableSchemaContext<T>
- For reusable schemas supporting both classes and interfaces
- Unified data context via
IXperienceDataContext
for centralized access to all context types - Extensible processor system for custom content processing and transformation pipelines
- Interface support in
ReusableSchemaContext
for maximum flexibility - Modern architecture with base classes reducing code duplication by 70%+
- Comprehensive debugging support with enhanced debugger displays, diagnostic logging, performance tracking, and telemetry integration
- Built on .NET 6/.NET 8/.NET 9, ensuring modern development practices
- Seamless integration with Xperience by Kentico
Quick Start
- Prerequisites: Ensure you have .NET 8/.NET 9 and Kentico Xperience installed.
- Installation: Install this project through NuGet.
Architecture Overview
XperienceCommunity.DataContext provides three specialized contexts for different content scenarios:
1. Content Item Context (IContentItemContext<T>
)
- Purpose: Query content hub items with strongly-typed expressions
- Use Case: Content items, reusable content blocks, structured data
- Type Constraint:
T
must implementIContentItemFieldSource
- Features: Full LINQ support, caching, linked items
2. Page Content Context (IPageContentContext<T>
)
- Purpose: Query web pages with channel and path filtering
- Use Case: Website pages, routing, channel-specific content
- Type Constraint:
T
must implementIWebPageFieldsSource
- Features: Channel filtering, path-based queries, page hierarchy
3. Reusable Schema Context (IReusableSchemaContext<T>
)
- Purpose: Query reusable schemas with maximum flexibility
- Use Case: Shared content schemas, interface-based content models
- Type Constraint: None - supports both classes and interfaces
- Features: Interface support, schema flexibility, reusable patterns
Unified Data Context (IXperienceDataContext
)
- Purpose: Centralized access to all context types
- Benefits: Single dependency injection, consistent API, easier testing
- Methods:
ForContentType<T>()
,ForPageContentType<T>()
,ForReusableSchema<T>()
Prerequisites
Before you begin, ensure you have met the following requirements:
- .NET: Make sure you have .NET 8, or .NET 9 installed on your development machine. You can download it from the official .NET download page.
- Xperience By Kentico Project: You need an existing Xperience By Kentico project. If you're new to Xperience By Kentico, start with the official documentation.
Installation
To integrate XperienceCommunity.DataContext into your Kentico Xperience project, follow these steps:
NuGet Package: Install the NuGet package via the Package Manager Console:
Install-Package XperienceCommunity.DataContext
Or via the .NET CLI:
dotnet add package XperienceCommunity.DataContext
Or add it directly to your
.csproj
file:<PackageReference Include="XperienceCommunity.Data.Context" Version="[latest-version]" />
Configure Services:
Prerequisites: Ensure Xperience by Kentico services are registered first:
// Required Kentico services (typically already configured in Xperience projects) builder.Services.AddKentico(); builder.Services.AddKenticoFeatures();
Register XperienceCommunity.DataContext:
// Method 1: Simple registration with optional cache timeout builder.Services.AddXperienceDataContext(cacheInMinutes: 30); // Method 2: Fluent builder with processors builder.Services.AddXperienceDataContext() .AddContentItemProcessor<BlogPost, BlogPostProcessor>() .AddPageContentProcessor<LandingPage, LandingPageProcessor>() .SetCacheTimeout(30); // Method 3: Basic registration (uses default 15-minute cache) builder.Services.AddXperienceDataContext();
Required Dependencies: The library depends on these Kentico services being available:
IProgressiveCache
- For caching query resultsIWebsiteChannelContext
- For channel and preview contextIContentQueryExecutor
- For executing Kentico content queries
These are automatically provided when you call
AddKentico()
in a standard Xperience by Kentico project.
Injecting the IContentItemContext
into a Class
To leverage the IContentItemContext
in your classes, you need to inject it via dependency injection. The IContentItemContext
requires a class that implements the IContentItemFieldSource
interface. For instance, you might have a GenericContent
class designed for the Content Hub.
Querying Content Items Example
Assuming you have a GenericContent
class that implements IContentItemFieldSource
, you can inject the IContentItemContext<GenericContent>
into your classes as follows:
public class MyService
{
private readonly IContentItemContext<GenericContent> _contentItemContext;
public MyService(IContentItemContext<GenericContent> contentItemContext)
{
_contentItemContext = contentItemContext;
}
// Example method using the _contentItemContext
public async Task<GenericContent?> GetContentItemAsync(Guid contentItemGUID)
{
return await _contentItemContext
.FirstOrDefaultAsync(x => x.SystemFields.ContentItemGUID == contentItemGUID);
}
}
This setup allows you to utilize the fluent API provided by IContentItemContext
to interact with content items in a type-safe manner, enhancing the development experience with Kentico Xperience.
Example Usage
Here's a quick example to show how you can use XperienceCommunity.DataContext in your project:
var result = await _context
.WithLinkedItems(1)
.FirstOrDefaultAsync(x => x.SystemFields.ContentItemGUID == selected.Identifier, HttpContext.RequestAborted);
This example demonstrates how to asynchronously retrieve the first content item that matches a given GUID, with a single level of linked items included, using the fluent API provided by XperienceCommunity.DataContext.
Querying Page Content Example
Assuming you have a GenericPage
class that implements IWebPageFieldsSource
, you can inject the IPageContentContext<GenericPage>
into your classes as follows:
public class GenericPageController : Controller
{
private readonly IPageContentContext<GenericPage> _pageContext;
private readonly IWebPageDataContextRetriever _webPageDataContextRetriever;
public GenericPageController(
IPageContentContext<GenericPage> pageContext,
IWebPageDataContextRetriever webPageDataContextRetriever)
{
_pageContext = pageContext;
_webPageDataContextRetriever = webPageDataContextRetriever;
}
// Example method using the _pageContext
public async Task<IActionResult> IndexAsync()
{
var page = _webPageDataContextRetriever.Retrieve().WebPage;
if (page == null)
{
return NotFound();
}
var content = await _pageContext
.FirstOrDefaultAsync(x => x.SystemFields.WebPageItemID == page.WebPageItemID, HttpContext.RequestAborted);
if (content == null)
{
return NotFound();
}
return View(content);
}
}
This example demonstrates how to asynchronously retrieve the first page content item that matches a given ID, using the fluent API provided by XperienceCommunity.DataContext.
Using IXperienceDataContext Example:
To demonstrate how to use the IXperienceDataContext interface, consider the following example:
public class ContentService
{
private readonly IXperienceDataContext _dataContext;
public ContentService(IXperienceDataContext dataContext)
{
_dataContext = dataContext;
}
public async Task<GenericContent?> GetContentItemAsync(Guid contentItemGUID)
{
var contentItemContext = _dataContext.ForContentType<GenericContent>();
return await contentItemContext.FirstOrDefaultAsync(x => x.SystemFields.ContentItemGUID == contentItemGUID);
}
public async Task<GenericPage?> GetPageContentAsync(Guid pageGUID)
{
var pageContentContext = _dataContext.ForPageContentType<GenericPage>();
return await pageContentContext.FirstOrDefaultAsync(x => x.SystemFields.WebPageItemGUID == pageGUID);
}
}
In this example, the ContentService class uses the IXperienceDataContext interface to get contexts for content items and page content. This setup allows you to leverage the fluent API provided by IContentItemContext and IPageContentContext to interact with content items and page content in a type-safe manner.
Advanced Usage Examples
Working with Reusable Schema Context
The IReusableSchemaContext<T>
is the most flexible context, supporting both classes and interfaces. This is particularly useful for reusable content schemas:
// Define an interface for shared content
public interface ISharedContent
{
string Title { get; set; }
string Description { get; set; }
DateTime PublishDate { get; set; }
}
// Use the interface with ReusableSchemaContext
public class SharedContentService
{
private readonly IReusableSchemaContext<ISharedContent> _schemaContext;
public SharedContentService(IReusableSchemaContext<ISharedContent> schemaContext)
{
_schemaContext = schemaContext;
}
public async Task<IEnumerable<ISharedContent>> GetRecentContentAsync()
{
return await _schemaContext
.Where(x => x.PublishDate >= DateTime.Now.AddDays(-30))
.OrderByDescending(x => x.PublishDate)
.ToListAsync();
}
}
Custom Processors and Extensibility
The library includes an extensible processor system for custom content transformations. There are specialized processor interfaces for different content types:
Content Item Processors
For content hub items, implement IContentItemProcessor<T>
:
// Custom processor for content items
public class BlogPostProcessor : IContentItemProcessor<BlogPost>
{
public int Order => 1; // Execution order
public async Task ProcessAsync(BlogPost content, CancellationToken cancellationToken = default)
{
// Custom processing logic for blog posts
// E.g., update search index, generate thumbnails, etc.
content.ProcessedDate = DateTime.UtcNow;
// Async processing example
await SomeAsyncOperation(content, cancellationToken);
}
}
Page Content Processors
For web pages, implement IPageContentProcessor<T>
:
// Custom processor for page content
public class LandingPageProcessor : IPageContentProcessor<LandingPage>
{
public int Order => 2; // Execution order
public async Task ProcessAsync(LandingPage content, CancellationToken cancellationToken = default)
{
// Custom processing logic for landing pages
// E.g., analytics tracking, personalization, etc.
content.ViewCount++;
await UpdateAnalytics(content, cancellationToken);
}
}
Expression Processors
For custom LINQ expression handling, implement IExpressionProcessor<T>
:
// Custom expression processor for specialized queries
public class CustomMethodProcessor : IExpressionProcessor<MethodCallExpression>
{
private readonly IExpressionContext _context;
public CustomMethodProcessor(IExpressionContext context)
{
_context = context;
}
public bool CanProcess(Expression expression)
{
// Define when this processor should handle expressions
return expression is MethodCallExpression method &&
method.Method.Name == "HasCustomTag";
}
public void Process(MethodCallExpression expression)
{
// Transform the expression for your specific needs
// Implementation details depend on your requirements
var methodCall = expression;
var tagValue = GetTagValue(methodCall);
_context.AddParameter("CustomTag", tagValue);
_context.AddWhereAction(where => where.WhereEquals("Tags", tagValue));
}
}
Registration
Register your custom processors in the DI container using the fluent configuration:
// Register content processors with fluent builder
services.AddXperienceDataContext()
.AddContentItemProcessor<BlogPost, BlogPostProcessor>()
.AddPageContentProcessor<LandingPage, LandingPageProcessor>();
// Or register expression processors directly
services.AddScoped<IExpressionProcessor, CustomMethodProcessor>();
// Alternative: Register with cache configuration
services.AddXperienceDataContext(cacheInMinutes: 30);
// Then register processors separately
services.AddScoped<IContentItemProcessor<BlogPost>, BlogPostProcessor>();
services.AddScoped<IPageContentProcessor<LandingPage>, LandingPageProcessor>();
Unified Data Context Patterns
Using IXperienceDataContext
for centralized content management:
public class ContentManagementService
{
private readonly IXperienceDataContext _dataContext;
public ContentManagementService(IXperienceDataContext dataContext)
{
_dataContext = dataContext;
}
public async Task<T?> GetContentByIdAsync<T>(int id) where T : class, IContentItemFieldSource
{
return await _dataContext
.ForContentType<T>()
.FirstOrDefaultAsync(x => x.SystemFields.ContentItemID == id);
}
public async Task<T?> GetPageByPathAsync<T>(string path) where T : class, IWebPageFieldsSource
{
return await _dataContext
.ForPageContentType<T>()
.FirstOrDefaultAsync(x => x.SystemFields.WebPageUrlPath == path);
}
public async Task<IEnumerable<T>> GetSchemaContentAsync<T>() where T : class
{
return await _dataContext
.ForReusableSchema<T>()
.ToListAsync();
}
}
Performance Optimization with Caching
The library includes built-in caching with automatic cache dependency management:
public class OptimizedContentService
{
private readonly IContentItemContext<Article> _contentContext;
public OptimizedContentService(IContentItemContext<Article> contentContext)
{
_contentContext = contentContext;
}
public async Task<IEnumerable<Article>> GetFeaturedArticlesAsync()
{
// Caching is automatically handled with proper cache dependencies
return await _contentContext
.WithLinkedItems(2) // Include linked items up to 2 levels
.Where(x => x.IsFeatured == true)
.OrderByDescending(x => x.PublishDate)
.ToListAsync();
}
}
Debugging & Diagnostics
XperienceCommunity.DataContext provides comprehensive debugging and diagnostic capabilities to help developers troubleshoot issues and gain insights into query execution:
Enhanced Debugger Support
All key classes include rich [DebuggerDisplay]
attributes for better debugging experience:
// ExpressionContext shows: Parameters: 3, Members: User.Name, WhereActions: 2
// BaseDataContext shows: ContentType: BlogPost, Language: en-US, Parameters: 2, HasQuery: true
var context = dataContext.ForContentType<BlogPost>()
.Where(x => x.Title.Contains("Tutorial"));
// Examine context in debugger to see detailed state information
Diagnostic Logging
Enable detailed execution tracking and performance monitoring:
// Enable diagnostics globally
DataContextDiagnostics.DiagnosticsEnabled = true;
DataContextDiagnostics.TraceLevel = LogLevel.Debug;
// Or enable for specific operations
var context = dataContext.ForContentType<BlogPost>()
.EnableDiagnostics(LogLevel.Debug)
.Where(x => x.IsPublished);
// Get diagnostic reports
string report = context.GetDiagnosticReport("ExpressionProcessing");
var stats = context.GetPerformanceStats();
Performance Tracking
Built-in performance counters track query execution metrics:
// Access performance statistics
var avgTime = ProcessorSupportedQueryExecutor<BlogPost, IProcessor<BlogPost>>.AverageProcessingTimeMs;
var totalQueries = ProcessorSupportedQueryExecutor<BlogPost, IProcessor<BlogPost>>.TotalExecutions;
// Detailed timing for specific operations
var results = await context.ExecuteWithDiagnostics(
"ComplexQuery",
async ctx => await ctx.Where(x => x.Tags.Contains("tutorial")).ToListAsync()
);
Telemetry Integration
Automatic OpenTelemetry/Activity support for distributed tracing:
// Activities are created with detailed tags:
// - contentType, processorCount, executionTimeMs, resultCount, error status
// ActivitySource name: "XperienceCommunity.Data.Context.QueryExecution"
Custom Diagnostics
Add your own diagnostic logging:
var context = dataContext.ForContentType<BlogPost>()
.LogDiagnostic("Starting complex operation")
.Where(x => x.Category == "Technology")
.LogDiagnostic("Applied filters", LogLevel.Debug);
// Get detailed debug information
Console.WriteLine(context.ToDebugString());
For comprehensive debugging guidance, see Debugging Guide
Troubleshooting & Best Practices
Common Issues and Solutions
1. Type Constraint Errors
Problem: The type 'T' cannot be used as type parameter 'T' in the generic type or method
Solution: Ensure your types implement the required interfaces:
IContentItemContext<T>
requiresT : IContentItemFieldSource
IPageContentContext<T>
requiresT : IWebPageFieldsSource
IReusableSchemaContext<T>
has no constraints - supports any type
2. Interface Support in ReusableSchemaContext
Problem: Need to use interfaces instead of concrete classes
Solution: Use IReusableSchemaContext<T>
which supports both classes and interfaces:
// This works with interfaces
IReusableSchemaContext<IMyInterface> context;
// This also works with classes
IReusableSchemaContext<MyClass> context;
3. Processor Registration
Problem: Custom processors not being recognized
Solution: Register your processors in the DI container:
services.AddScoped<IExpressionProcessor, CustomProcessor>();
services.AddXperienceDataContext(); // Call after registering processors
4. Performance Optimization
Best Practices:
- Use
WithLinkedItems()
only when needed - Prefer
FirstOrDefaultAsync()
overToListAsync().FirstOrDefault()
- Leverage built-in caching - don't implement your own caching layer
- Use cancellation tokens for long-running operations
5. Testing with Contexts
Recommendation: Use IXperienceDataContext
for easier mocking in unit tests:
// Easy to mock
public class MyService
{
private readonly IXperienceDataContext _dataContext;
public MyService(IXperienceDataContext dataContext)
{
_dataContext = dataContext;
}
}
Architecture Notes
The library uses a three-tier architecture:
- Base Classes:
BaseDataContext<T, TExecutor>
andProcessorSupportedQueryExecutor<T, TProcessor>
- Specialized Contexts: Content, Page, and Reusable Schema contexts
- Unified Interface:
IXperienceDataContext
for centralized access
This design reduces code duplication by 70%+ while maintaining type safety and flexibility.
Built With
- Xperience By Kentico - Kentico Xperience
- NuGet - Dependency Management
Versioning
We use SemVer for versioning. For the versions available, see the tags on this repository.
Authors
- Brandon Henricks - Initial work - Brandon Henricks
License
This project is licensed under the MIT License - see the LICENSE file for details
Acknowledgments
- Mike Wills
- David Rector
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 was computed. 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. |
-
net8.0
- CSharpFunctionalExtensions (>= 3.6.0)
-
net9.0
- CSharpFunctionalExtensions (>= 3.6.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
- Refactor: Remove unnecessary using directives (83b41f2)