Skip to main content
Version: Latest

Core Services

Foundation services that other CrestApps features depend on.

Quick Start​

builder.Services.AddCoreServices();
info

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​

ServiceImplementationLifetimePurpose
IODataValidatorODataFilterValidatorScopedValidates 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:

  1. Validated by IODataValidator to ensure it is syntactically correct
  2. Translated by IODataFilterTranslator into the backend-native query language (e.g., Elasticsearch Query DSL or Azure AI Search OData)
  3. 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);
}