Core Services
Foundation services that other CrestApps features depend on.
Quick Start
builder.Services.AddCoreServices();
You rarely need to call this directly — AddCoreAIServices() chains it automatically.
Problem & Solution
Every feature in the framework shares common concerns like OData filter validation and base service patterns. AddCoreServices() registers these shared services so that higher-level features can depend on them without duplication.
Services Registered
| Service | Implementation | Lifetime | Purpose |
|---|---|---|---|
IODataValidator | ODataFilterValidator | Scoped | Validates OData $filter expressions |
OData Filter Examples
OData filters are used by data source backends to narrow search results. The IODataValidator service validates filter expressions before they are translated and sent to the backend.
Valid Filter Expressions
category eq 'support'
status ne 'archived'
priority gt 3
createdDate ge 2024-01-01T00:00:00Z
category eq 'support' and status eq 'active'
category eq 'support' or category eq 'billing'
contains(title, 'refund')
startswith(name, 'John')
not endswith(email, '@example.com')
(priority gt 3 and status eq 'open') or category eq 'urgent'
Invalid Filter Expressions
category = 'support' # Wrong operator (use 'eq', not '=')
category eq support # Missing quotes around string value
and category eq 'support' # Leading logical operator
category eq 'support' and # Trailing logical operator
category eq # Missing value
Using the Validator
public sealed class MyDataSourceService
{
private readonly IODataValidator _validator;
public MyDataSourceService(IODataValidator validator)
{
_validator = validator;
}
public void ApplyFilter(string userFilter)
{
if (!string.IsNullOrEmpty(userFilter) && !_validator.IsValidFilter(userFilter))
{
throw new ArgumentException($"Invalid OData filter expression: {userFilter}");
}
// Filter is safe to pass to the backend
}
}
When You Need This
The OData filter validator is primarily used by the data source system. When a data source is configured with filter criteria (e.g., "only return documents where category eq 'support'"), the filter expression is:
- Validated by
IODataValidatorto ensure it is syntactically correct - Translated by
IODataFilterTranslatorinto the backend-native query language (e.g., Elasticsearch Query DSL or Azure AI Search OData) - Applied during vector search to narrow results
This prevents malformed filters from causing backend errors or unexpected behavior.
The chat interaction hub applies the same check. ChatInteractionHubBase.SaveSettings validates the filter setting with the registered IODataValidator before any settings handler runs. An invalid filter rejects the whole save: the caller receives ReceiveError with the message from GetSettingsValidationMessage("filter"), and the interaction keeps the settings it had. DataSourceChatInteractionSettingsHandler also keeps the stored filter (and logs a warning) when it receives an invalid one, for a host that saves the settings another way.
When a filter is set but no IODataFilterTranslator is registered for the knowledge base index provider, the filter cannot be applied. Both the preemptive retrieval and the search_data_sources tool (DataSourceSearchTool) then log a warning ("No OData filter translator available for provider '…'. Filter will be ignored.") and search without the filter.
User-provided OData filter
│
▼
IODataValidator.IsValidFilter() → false? → reject with error
│ true
▼
IODataFilterTranslator.Translate() → backend-native filter
│
▼
IDataSourceContentManager.SearchAsync(..., filter)
Implementing a Custom Validator
If you need stricter validation rules (e.g., allow only specific field names or operators), implement IODataValidator:
public sealed class StrictODataValidator : IODataValidator
{
private static readonly HashSet<string> _allowedFields =
[
"category",
"status",
"priority",
"createdDate",
];
public bool IsValidFilter(string filter)
{
if (string.IsNullOrWhiteSpace(filter))
{
return false;
}
// First, check basic OData syntax (delegate to default validator logic)
if (!IsValidODataSyntax(filter))
{
return false;
}
// Then, check that only allowed fields are referenced
foreach (var field in _allowedFields)
{
// Custom field validation logic
}
return true;
}
private static bool IsValidODataSyntax(string filter)
{
// Basic OData expression validation
// Check for balanced parentheses, valid operators, etc.
return !string.IsNullOrWhiteSpace(filter);
}
}
Register it to replace the default:
services.AddScoped<IODataValidator, StrictODataValidator>();
Interfaces
IODataValidator
Validates OData filter strings before they are passed to data source backends (Elasticsearch, Azure AI Search).
public interface IODataValidator
{
bool IsValidFilter(string filter);
}