Split the Examine specifics from API query service (#14257)
* Split the Examine specifics from API query service to a provider based model * Review changes: Use paged model as provider return value + add logging
This commit is contained in:
@@ -31,10 +31,6 @@ public abstract class ContentApiControllerBase : DeliveryApiControllerBase
|
||||
.WithTitle("Filter option not found")
|
||||
.WithDetail("One of the attempted 'filter' options does not exist")
|
||||
.Build()),
|
||||
ApiContentQueryOperationStatus.IndexNotFound => BadRequest(new ProblemDetailsBuilder()
|
||||
.WithTitle("Examine index not found")
|
||||
.WithDetail($"No index found with name {Constants.UmbracoIndexes.DeliveryApiContentIndexName}")
|
||||
.Build()),
|
||||
ApiContentQueryOperationStatus.SelectorOptionNotFound => BadRequest(new ProblemDetailsBuilder()
|
||||
.WithTitle("Selector option not found")
|
||||
.WithDetail("The attempted 'fetch' option does not exist")
|
||||
|
||||
@@ -28,6 +28,7 @@ public static class UmbracoBuilderExtensions
|
||||
builder.Services.AddSingleton<IRequestStartItemProviderAccessor, RequestContextRequestStartItemProviderAccessor>();
|
||||
builder.Services.AddSingleton<IApiAccessService, ApiAccessService>();
|
||||
builder.Services.AddSingleton<IApiContentQueryService, ApiContentQueryService>();
|
||||
builder.Services.AddSingleton<IApiContentQueryProvider, ApiContentQueryProvider>();
|
||||
|
||||
builder.Services.ConfigureOptions<ConfigureUmbracoDeliveryApiSwaggerGenOptions>();
|
||||
builder.AddUmbracoApiOpenApiUI();
|
||||
|
||||
164
src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs
Normal file
164
src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using Examine;
|
||||
using Examine.Search;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.New.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This is the Examine implementation of content querying for the Delivery API.
|
||||
/// </summary>
|
||||
internal sealed class ApiContentQueryProvider : IApiContentQueryProvider
|
||||
{
|
||||
private const string ItemIdFieldName = "itemId";
|
||||
private readonly IExamineManager _examineManager;
|
||||
private readonly ILogger<ApiContentQueryProvider> _logger;
|
||||
private readonly string _fallbackGuidValue;
|
||||
private readonly Dictionary<string, FieldType> _fieldTypes;
|
||||
|
||||
public ApiContentQueryProvider(
|
||||
IExamineManager examineManager,
|
||||
ContentIndexHandlerCollection indexHandlers,
|
||||
ILogger<ApiContentQueryProvider> logger)
|
||||
{
|
||||
_examineManager = examineManager;
|
||||
_logger = logger;
|
||||
|
||||
// A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string
|
||||
// It is set to a random guid since this would be highly unlikely to yield any results
|
||||
_fallbackGuidValue = Guid.NewGuid().ToString("D");
|
||||
|
||||
// build a look-up dictionary of field types by field name
|
||||
_fieldTypes = indexHandlers
|
||||
.SelectMany(handler => handler.GetFields())
|
||||
.DistinctBy(field => field.FieldName)
|
||||
.ToDictionary(field => field.FieldName, field => field.FieldType, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public PagedModel<Guid> ExecuteQuery(SelectorOption selectorOption, IList<FilterOption> filterOptions, IList<SortOption> sortOptions, string culture, int skip, int take)
|
||||
{
|
||||
if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.DeliveryApiContentIndexName, out IIndex? index))
|
||||
{
|
||||
_logger.LogError("Could not find the index {IndexName} when attempting to execute a query.", Constants.UmbracoIndexes.DeliveryApiContentIndexName);
|
||||
return new PagedModel<Guid>();
|
||||
}
|
||||
|
||||
IBooleanOperation queryOperation = BuildSelectorOperation(selectorOption, index, culture);
|
||||
|
||||
ApplyFiltering(filterOptions, queryOperation);
|
||||
ApplySorting(sortOptions, queryOperation);
|
||||
|
||||
ISearchResults? results = queryOperation
|
||||
.SelectField(ItemIdFieldName)
|
||||
.Execute(QueryOptions.SkipTake(skip, take));
|
||||
|
||||
if (results is null)
|
||||
{
|
||||
// The query yield no results
|
||||
return new PagedModel<Guid>();
|
||||
}
|
||||
|
||||
Guid[] items = results
|
||||
.Where(r => r.Values.ContainsKey(ItemIdFieldName))
|
||||
.Select(r => Guid.Parse(r.Values[ItemIdFieldName]))
|
||||
.ToArray();
|
||||
|
||||
return new PagedModel<Guid>(results.TotalItemCount, items);
|
||||
}
|
||||
|
||||
public SelectorOption AllContentSelectorOption() => new()
|
||||
{
|
||||
FieldName = UmbracoExamineFieldNames.CategoryFieldName, Values = new[] { "content" }
|
||||
};
|
||||
|
||||
private IBooleanOperation BuildSelectorOperation(SelectorOption selectorOption, IIndex index, string culture)
|
||||
{
|
||||
IQuery query = index.Searcher.CreateQuery();
|
||||
|
||||
IBooleanOperation selectorOperation = selectorOption.Values.Length == 1
|
||||
? query.Field(selectorOption.FieldName, selectorOption.Values.First())
|
||||
: query.GroupedOr(new[] { selectorOption.FieldName }, selectorOption.Values);
|
||||
|
||||
// Item culture must be either the requested culture or "none"
|
||||
selectorOperation.And().GroupedOr(new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture }, culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue), "none");
|
||||
|
||||
return selectorOperation;
|
||||
}
|
||||
|
||||
private void ApplyFiltering(IList<FilterOption> filterOptions, IBooleanOperation queryOperation)
|
||||
{
|
||||
void HandleExact(IQuery query, string fieldName, string[] values)
|
||||
{
|
||||
if (values.Length == 1)
|
||||
{
|
||||
query.Field(fieldName, values[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
query.GroupedOr(new[] { fieldName }, values);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FilterOption filterOption in filterOptions)
|
||||
{
|
||||
var values = filterOption.Values.Any()
|
||||
? filterOption.Values
|
||||
: new[] { _fallbackGuidValue };
|
||||
|
||||
switch (filterOption.Operator)
|
||||
{
|
||||
case FilterOperation.Is:
|
||||
// TODO: test this for explicit word matching
|
||||
HandleExact(queryOperation.And(), filterOption.FieldName, values);
|
||||
break;
|
||||
case FilterOperation.IsNot:
|
||||
// TODO: test this for explicit word matching
|
||||
HandleExact(queryOperation.Not(), filterOption.FieldName, values);
|
||||
break;
|
||||
// TODO: Fix
|
||||
case FilterOperation.Contains:
|
||||
break;
|
||||
// TODO: Fix
|
||||
case FilterOperation.DoesNotContain:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySorting(IList<SortOption> sortOptions, IOrdering ordering)
|
||||
{
|
||||
foreach (SortOption sort in sortOptions)
|
||||
{
|
||||
if (_fieldTypes.TryGetValue(sort.FieldName, out FieldType fieldType) is false)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Sort implementation for field name {FieldName} does not match an index handler implementation, cannot resolve field type.",
|
||||
sort.FieldName);
|
||||
continue;
|
||||
}
|
||||
|
||||
SortType sortType = fieldType switch
|
||||
{
|
||||
FieldType.Number => SortType.Int,
|
||||
FieldType.Date => SortType.Long,
|
||||
FieldType.StringRaw => SortType.String,
|
||||
FieldType.StringAnalyzed => SortType.String,
|
||||
FieldType.StringSortable => SortType.String,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(fieldType))
|
||||
};
|
||||
|
||||
ordering = sort.Direction switch
|
||||
{
|
||||
Direction.Ascending => ordering.OrderBy(new SortableField(sort.FieldName, sortType)),
|
||||
Direction.Descending => ordering.OrderByDescending(new SortableField(sort.FieldName, sortType)),
|
||||
_ => ordering
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,35 @@
|
||||
using Examine;
|
||||
using Examine.Search;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Api.Delivery.Indexing.Selectors;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.New.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal sealed class ApiContentQueryService : IApiContentQueryService
|
||||
{
|
||||
private const string ItemIdFieldName = "itemId";
|
||||
private readonly IExamineManager _examineManager;
|
||||
private readonly IRequestStartItemProviderAccessor _requestStartItemProviderAccessor;
|
||||
private readonly SelectorHandlerCollection _selectorHandlers;
|
||||
private readonly FilterHandlerCollection _filterHandlers;
|
||||
private readonly SortHandlerCollection _sortHandlers;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly ILogger<ApiContentQueryService> _logger;
|
||||
private readonly string _fallbackGuidValue;
|
||||
private readonly Dictionary<string, FieldType> _fieldTypes;
|
||||
private readonly IApiContentQueryProvider _apiContentQueryProvider;
|
||||
|
||||
public ApiContentQueryService(
|
||||
IExamineManager examineManager,
|
||||
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
|
||||
SelectorHandlerCollection selectorHandlers,
|
||||
FilterHandlerCollection filterHandlers,
|
||||
SortHandlerCollection sortHandlers,
|
||||
ContentIndexHandlerCollection indexHandlers,
|
||||
ILogger<ApiContentQueryService> logger,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IApiContentQueryProvider apiContentQueryProvider)
|
||||
{
|
||||
_examineManager = examineManager;
|
||||
_requestStartItemProviderAccessor = requestStartItemProviderAccessor;
|
||||
_selectorHandlers = selectorHandlers;
|
||||
_filterHandlers = filterHandlers;
|
||||
_sortHandlers = sortHandlers;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_logger = logger;
|
||||
|
||||
// A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string
|
||||
// It is set to a random guid since this would be highly unlikely to yield any results
|
||||
_fallbackGuidValue = Guid.NewGuid().ToString("D");
|
||||
|
||||
// build a look-up dictionary of field types by field name
|
||||
_fieldTypes = indexHandlers
|
||||
.SelectMany(handler => handler.GetFields())
|
||||
.DistinctBy(field => field.FieldName)
|
||||
.ToDictionary(field => field.FieldName, field => field.FieldType, StringComparer.InvariantCultureIgnoreCase);
|
||||
_apiContentQueryProvider = apiContentQueryProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -59,198 +37,78 @@ internal sealed class ApiContentQueryService : IApiContentQueryService
|
||||
{
|
||||
var emptyResult = new PagedModel<Guid>();
|
||||
|
||||
if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.DeliveryApiContentIndexName, out IIndex? apiIndex))
|
||||
{
|
||||
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.IndexNotFound, emptyResult);
|
||||
}
|
||||
|
||||
IQuery baseQuery = apiIndex.Searcher.CreateQuery();
|
||||
|
||||
// Handle Selecting
|
||||
IBooleanOperation? queryOperation = HandleSelector(fetch, baseQuery);
|
||||
|
||||
// If no Selector could be found, we return no results
|
||||
if (queryOperation is null)
|
||||
SelectorOption? selectorOption = GetSelectorOption(fetch);
|
||||
if (selectorOption is null)
|
||||
{
|
||||
// If no Selector could be found, we return no results
|
||||
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.SelectorOptionNotFound, emptyResult);
|
||||
}
|
||||
|
||||
// Item culture must be either the requested culture or "none"
|
||||
var culture = CurrentCulture();
|
||||
queryOperation.And().GroupedOr(new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture }, culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue), "none");
|
||||
|
||||
// Handle Filtering
|
||||
var canApplyFiltering = CanHandleFiltering(filters, queryOperation);
|
||||
|
||||
// If there is an invalid Filter option, we return no results
|
||||
if (canApplyFiltering is false)
|
||||
var filterOptions = new List<FilterOption>();
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.FilterOptionNotFound, emptyResult);
|
||||
FilterOption? filterOption = GetFilterOption(filter);
|
||||
if (filterOption is null)
|
||||
{
|
||||
// If there is an invalid Filter option, we return no results
|
||||
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.FilterOptionNotFound, emptyResult);
|
||||
}
|
||||
|
||||
filterOptions.Add(filterOption);
|
||||
}
|
||||
|
||||
// Handle Sorting
|
||||
IOrdering? sortQuery = HandleSorting(sorts, queryOperation);
|
||||
|
||||
// If there is an invalid Sort option, we return no results
|
||||
if (sortQuery is null)
|
||||
var sortOptions = new List<SortOption>();
|
||||
foreach (var sort in sorts)
|
||||
{
|
||||
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.SortOptionNotFound, emptyResult);
|
||||
SortOption? sortOption = GetSortOption(sort);
|
||||
if (sortOption is null)
|
||||
{
|
||||
// If there is an invalid Sort option, we return no results
|
||||
return Attempt.FailWithStatus(ApiContentQueryOperationStatus.SortOptionNotFound, emptyResult);
|
||||
}
|
||||
|
||||
sortOptions.Add(sortOption);
|
||||
}
|
||||
|
||||
ISearchResults? results = sortQuery
|
||||
.SelectField(ItemIdFieldName)
|
||||
.Execute(QueryOptions.SkipTake(skip, take));
|
||||
var culture = _variationContextAccessor.VariationContext?.Culture ?? string.Empty;
|
||||
|
||||
if (results is null)
|
||||
{
|
||||
// The query yield no results
|
||||
return Attempt.SucceedWithStatus(ApiContentQueryOperationStatus.Success, emptyResult);
|
||||
}
|
||||
|
||||
Guid[] items = results
|
||||
.Where(r => r.Values.ContainsKey(ItemIdFieldName))
|
||||
.Select(r => Guid.Parse(r.Values[ItemIdFieldName]))
|
||||
.ToArray();
|
||||
|
||||
return Attempt.SucceedWithStatus(ApiContentQueryOperationStatus.Success, new PagedModel<Guid>(results.TotalItemCount, items));
|
||||
PagedModel<Guid> result = _apiContentQueryProvider.ExecuteQuery(selectorOption, filterOptions, sortOptions, culture, skip, take);
|
||||
return Attempt.SucceedWithStatus(ApiContentQueryOperationStatus.Success, result);
|
||||
}
|
||||
|
||||
private IBooleanOperation? HandleSelector(string? fetch, IQuery baseQuery)
|
||||
private SelectorOption? GetSelectorOption(string? fetch)
|
||||
{
|
||||
string? fieldName = null;
|
||||
string[] fieldValues = Array.Empty<string>();
|
||||
|
||||
if (fetch is not null)
|
||||
{
|
||||
ISelectorHandler? selectorHandler = _selectorHandlers.FirstOrDefault(h => h.CanHandle(fetch));
|
||||
SelectorOption? selector = selectorHandler?.BuildSelectorOption(fetch);
|
||||
|
||||
if (selector is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
fieldName = selector.FieldName;
|
||||
fieldValues = selector.Values.Any()
|
||||
? selector.Values
|
||||
: new[] { _fallbackGuidValue };
|
||||
return selectorHandler?.BuildSelectorOption(fetch);
|
||||
}
|
||||
|
||||
// Take into account the "start-item" header if present, as it defines a starting root node to query from
|
||||
if (fieldName is null && _requestStartItemProviderAccessor.TryGetValue(out IRequestStartItemProvider? requestStartItemProvider))
|
||||
if (_requestStartItemProviderAccessor.TryGetValue(out IRequestStartItemProvider? requestStartItemProvider))
|
||||
{
|
||||
IPublishedContent? startItem = requestStartItemProvider.GetStartItem();
|
||||
if (startItem is not null)
|
||||
{
|
||||
// Reusing the boolean operation of the "Descendants" selector, as we want to get all the nodes from the given starting point
|
||||
fieldName = DescendantsSelectorIndexer.FieldName;
|
||||
fieldValues = new [] { startItem.Key.ToString() };
|
||||
return new SelectorOption
|
||||
{
|
||||
FieldName = DescendantsSelectorIndexer.FieldName, Values = new[] { startItem.Key.ToString() }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If no params or no fetch value, get everything from the index - this is a way to do that with Examine
|
||||
fieldName ??= UmbracoExamineFieldNames.CategoryFieldName;
|
||||
fieldValues = fieldValues.Any() ? fieldValues : new [] { "content" };
|
||||
|
||||
return fieldValues.Length == 1
|
||||
? baseQuery.Field(fieldName, fieldValues.First())
|
||||
: baseQuery.GroupedOr(new[] { fieldName }, fieldValues);
|
||||
return _apiContentQueryProvider.AllContentSelectorOption();
|
||||
}
|
||||
|
||||
private bool CanHandleFiltering(IEnumerable<string> filters, IBooleanOperation queryOperation)
|
||||
private FilterOption? GetFilterOption(string filter)
|
||||
{
|
||||
void HandleExact(IQuery query, string fieldName, string[] values)
|
||||
{
|
||||
if (values.Length == 1)
|
||||
{
|
||||
query.Field(fieldName, values[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
query.GroupedOr(new[] { fieldName }, values);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var filterValue in filters)
|
||||
{
|
||||
IFilterHandler? filterHandler = _filterHandlers.FirstOrDefault(h => h.CanHandle(filterValue));
|
||||
FilterOption? filter = filterHandler?.BuildFilterOption(filterValue);
|
||||
|
||||
if (filter is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var values = filter.Values.Any()
|
||||
? filter.Values
|
||||
: new[] { _fallbackGuidValue };
|
||||
|
||||
switch (filter.Operator)
|
||||
{
|
||||
case FilterOperation.Is:
|
||||
// TODO: test this for explicit word matching
|
||||
HandleExact(queryOperation.And(), filter.FieldName, values);
|
||||
break;
|
||||
case FilterOperation.IsNot:
|
||||
// TODO: test this for explicit word matching
|
||||
HandleExact(queryOperation.Not(), filter.FieldName, values);
|
||||
break;
|
||||
// TODO: Fix
|
||||
case FilterOperation.Contains:
|
||||
break;
|
||||
// TODO: Fix
|
||||
case FilterOperation.DoesNotContain:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
IFilterHandler? filterHandler = _filterHandlers.FirstOrDefault(h => h.CanHandle(filter));
|
||||
return filterHandler?.BuildFilterOption(filter);
|
||||
}
|
||||
|
||||
private IOrdering? HandleSorting(IEnumerable<string> sorts, IBooleanOperation queryCriteria)
|
||||
private SortOption? GetSortOption(string sort)
|
||||
{
|
||||
IOrdering? orderingQuery = null;
|
||||
|
||||
foreach (var sortValue in sorts)
|
||||
{
|
||||
ISortHandler? sortHandler = _sortHandlers.FirstOrDefault(h => h.CanHandle(sortValue));
|
||||
SortOption? sort = sortHandler?.BuildSortOption(sortValue);
|
||||
|
||||
if (sort is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_fieldTypes.TryGetValue(sort.FieldName, out FieldType fieldType) is false)
|
||||
{
|
||||
_logger.LogWarning("Sort implementation for field name {FieldName} does not match an index handler implementation, cannot resolve field type.", sort.FieldName);
|
||||
continue;
|
||||
}
|
||||
|
||||
SortType sortType = fieldType switch
|
||||
{
|
||||
FieldType.Number => SortType.Int,
|
||||
FieldType.Date => SortType.Long,
|
||||
FieldType.StringRaw => SortType.String,
|
||||
FieldType.StringAnalyzed => SortType.String,
|
||||
FieldType.StringSortable => SortType.String,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(fieldType))
|
||||
};
|
||||
|
||||
orderingQuery = sort.Direction switch
|
||||
{
|
||||
Direction.Ascending => queryCriteria.OrderBy(new SortableField(sort.FieldName, sortType)),
|
||||
Direction.Descending => queryCriteria.OrderByDescending(new SortableField(sort.FieldName, sortType)),
|
||||
_ => orderingQuery
|
||||
};
|
||||
}
|
||||
|
||||
// Keep the index sorting as default
|
||||
return orderingQuery ?? queryCriteria.OrderBy();
|
||||
ISortHandler? sortHandler = _sortHandlers.FirstOrDefault(h => h.CanHandle(sort));
|
||||
return sortHandler?.BuildSortOption(sort);
|
||||
}
|
||||
|
||||
private string CurrentCulture()
|
||||
=> _variationContextAccessor.VariationContext?.Culture ?? string.Empty;
|
||||
}
|
||||
|
||||
26
src/Umbraco.Core/DeliveryApi/IApiContentQueryProvider.cs
Normal file
26
src/Umbraco.Core/DeliveryApi/IApiContentQueryProvider.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Umbraco.New.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
/// <summary>
|
||||
/// Concrete implementation of content querying (e.g. based on Examine)
|
||||
/// </summary>
|
||||
public interface IApiContentQueryProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a page of item ids that passed the search criteria.
|
||||
/// </summary>
|
||||
/// <param name="selectorOption">The selector option of the search criteria.</param>
|
||||
/// <param name="filterOptions">The filter options of the search criteria.</param>
|
||||
/// <param name="sortOptions">The sorting options of the search criteria.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="skip">Number of search results to skip (for pagination).</param>
|
||||
/// <param name="take">Number of search results to retrieve (for pagination).</param>
|
||||
/// <returns>A paged model containing the resulting IDs and the total number of results that matching the search criteria.</returns>
|
||||
PagedModel<Guid> ExecuteQuery(SelectorOption selectorOption, IList<FilterOption> filterOptions, IList<SortOption> sortOptions, string culture, int skip, int take);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a selector option that can be applied to fetch "all content" (i.e. if a selector option is not present when performing a search).
|
||||
/// </summary>
|
||||
SelectorOption AllContentSelectorOption();
|
||||
}
|
||||
@@ -4,7 +4,6 @@ public enum ApiContentQueryOperationStatus
|
||||
{
|
||||
Success,
|
||||
FilterOptionNotFound,
|
||||
IndexNotFound,
|
||||
SelectorOptionNotFound,
|
||||
SortOptionNotFound
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user