diff --git a/Kentico.Kontent.Delivery.Tests/Builders/DeliveryOptions/DeliveryOptionsBuilderTests.cs b/Kentico.Kontent.Delivery.Tests/Builders/DeliveryOptions/DeliveryOptionsBuilderTests.cs
index 8226593a..b1bc3142 100644
--- a/Kentico.Kontent.Delivery.Tests/Builders/DeliveryOptions/DeliveryOptionsBuilderTests.cs
+++ b/Kentico.Kontent.Delivery.Tests/Builders/DeliveryOptions/DeliveryOptionsBuilderTests.cs
@@ -109,6 +109,19 @@ public void BuildWithWaitForLoadingNewContent()
Assert.True(deliveryOptions.WaitForLoadingNewContent);
}
+ [Fact]
+ public void BuildWithIncludeTotalCount()
+ {
+ var deliveryOptions = DeliveryOptionsBuilder
+ .CreateInstance()
+ .WithProjectId(Guid.NewGuid())
+ .UseProductionApi()
+ .IncludeTotalCount()
+ .Build();
+
+ Assert.True(deliveryOptions.IncludeTotalCount);
+ }
+
[Fact]
public void BuildWithCustomEndpointForPreviewApi()
{
diff --git a/Kentico.Kontent.Delivery.Tests/DeliveryClientTests.cs b/Kentico.Kontent.Delivery.Tests/DeliveryClientTests.cs
index 6792ee0f..40c2af03 100644
--- a/Kentico.Kontent.Delivery.Tests/DeliveryClientTests.cs
+++ b/Kentico.Kontent.Delivery.Tests/DeliveryClientTests.cs
@@ -412,10 +412,10 @@ public async void GetTaxonomiesAsync()
public async void QueryParameters()
{
- string url = $"{_baseUrl}/items?elements.personas%5Ball%5D=barista%2Ccoffee%2Cblogger&elements.personas%5Bany%5D=barista%2Ccoffee%2Cblogger&system.sitemap_locations%5Bcontains%5D=cafes&elements.product_name=Hario%20V60&elements.price%5Bgt%5D=1000&elements.price%5Bgte%5D=50&system.type%5Bin%5D=cafe%2Ccoffee&elements.price%5Blt%5D=10&elements.price%5Blte%5D=4&elements.country%5Brange%5D=Guatemala%2CNicaragua&depth=2&elements=price%2Cproduct_name&limit=10&order=elements.price%5Bdesc%5D&skip=2&language=en";
+ string url = $"{_baseUrl}/items?elements.personas%5Ball%5D=barista%2Ccoffee%2Cblogger&elements.personas%5Bany%5D=barista%2Ccoffee%2Cblogger&system.sitemap_locations%5Bcontains%5D=cafes&elements.product_name=Hario%20V60&elements.price%5Bgt%5D=1000&elements.price%5Bgte%5D=50&system.type%5Bin%5D=cafe%2Ccoffee&elements.price%5Blt%5D=10&elements.price%5Blte%5D=4&elements.country%5Brange%5D=Guatemala%2CNicaragua&depth=2&elements=price%2Cproduct_name&limit=10&order=elements.price%5Bdesc%5D&skip=2&language=en&includeTotalCount";
_mockHttp
.When($"{url}")
- .Respond("application/json", " { 'items': [],'modular_content': {},'pagination': {'skip': 2,'limit': 10,'count': 0,'next_page': ''}}");
+ .Respond("application/json", " { 'items': [],'modular_content': {},'pagination': {'skip': 2,'limit': 10,'count': 0, 'total_count': 0, 'next_page': ''}}");
var client = InitializeDeliveryClientWithACustomTypeProvider(_mockHttp);
@@ -436,7 +436,8 @@ public async void QueryParameters()
new LimitParameter(10),
new OrderParameter("elements.price", SortOrder.Descending),
new SkipParameter(2),
- new LanguageParameter("en")
+ new LanguageParameter("en"),
+ new IncludeTotalCountParameter()
};
var response = await client.GetItemsAsync(parameters);
diff --git a/Kentico.Kontent.Delivery.Tests/Kentico.Kontent.Delivery.Tests.csproj b/Kentico.Kontent.Delivery.Tests/Kentico.Kontent.Delivery.Tests.csproj
index af8aa15b..0bc2f6e4 100644
--- a/Kentico.Kontent.Delivery.Tests/Kentico.Kontent.Delivery.Tests.csproj
+++ b/Kentico.Kontent.Delivery.Tests/Kentico.Kontent.Delivery.Tests.csproj
@@ -38,6 +38,7 @@
+
diff --git a/Kentico.Kontent.Delivery.Tests/QueryParameters/ContentTypeExtractorTests.cs b/Kentico.Kontent.Delivery.Tests/QueryParameters/ContentTypeExtractorTests.cs
index b7514b2b..6e41f916 100644
--- a/Kentico.Kontent.Delivery.Tests/QueryParameters/ContentTypeExtractorTests.cs
+++ b/Kentico.Kontent.Delivery.Tests/QueryParameters/ContentTypeExtractorTests.cs
@@ -49,7 +49,7 @@ public void ExtractParameters_WhenGivenTypeWithCodenameAndExistingParams_AddsCod
{
var existingParams = new List() { new SkipParameter(15) };
- var enhancedParams = new List(_client.ExtractParameters(existingParams));
+ var enhancedParams = new List(_client.EnsureContentTypeFilter(existingParams));
Assert.Equal(2, enhancedParams.Count);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type={CONTENT_TYPE_CODENAME}") != null);
@@ -58,7 +58,7 @@ public void ExtractParameters_WhenGivenTypeWithCodenameAndExistingParams_AddsCod
[Fact]
public void ExtractParameters_WhenGivenTypeWithCodename_CreatesNewParams()
{
- var enhancedParams = new List(_client.ExtractParameters());
+ var enhancedParams = new List(_client.EnsureContentTypeFilter());
Assert.Single(enhancedParams);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type={CONTENT_TYPE_CODENAME}") != null);
@@ -67,7 +67,7 @@ public void ExtractParameters_WhenGivenTypeWithCodename_CreatesNewParams()
[Fact]
public void ExtractParameters_WhenGivenTypeWithoutCodenameNoParams_CreatesEmptyParams()
{
- var enhancedParams = new List(_client.ExtractParameters());
+ var enhancedParams = new List(_client.EnsureContentTypeFilter());
Assert.Empty(enhancedParams);
}
@@ -77,7 +77,7 @@ public void ExtractParameters_WhenGivenTypeWithoutCodenameAndParams_ReturnsParam
{
var existingParams = new List() { new SkipParameter(15) };
- var enhancedParams = new List(_client.ExtractParameters(existingParams));
+ var enhancedParams = new List(_client.EnsureContentTypeFilter(existingParams));
Assert.Single(enhancedParams);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type=TypeWithoutContentTypeCodename") == null);
@@ -88,7 +88,7 @@ public void ExtractParameters_WhenGivenTypeWithCodenameAndExistingTypeParameter_
{
var existingParams = new List() { new EqualsFilter("system.type", CONTENT_TYPE_CODENAME) };
- var enhancedParams = new List(_client.ExtractParameters(existingParams));
+ var enhancedParams = new List(_client.EnsureContentTypeFilter(existingParams));
Assert.Single(enhancedParams);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type={CONTENT_TYPE_CODENAME}") != null);
diff --git a/Kentico.Kontent.Delivery.Tests/QueryParameters/IncludeTotalCountTests.cs b/Kentico.Kontent.Delivery.Tests/QueryParameters/IncludeTotalCountTests.cs
new file mode 100644
index 00000000..a0606781
--- /dev/null
+++ b/Kentico.Kontent.Delivery.Tests/QueryParameters/IncludeTotalCountTests.cs
@@ -0,0 +1,119 @@
+using System;
+using FakeItEasy;
+using FluentAssertions;
+using Kentico.Kontent.Delivery.Tests.Factories;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using RichardSzalay.MockHttp;
+using Xunit;
+
+namespace Kentico.Kontent.Delivery.Tests.QueryParameters
+{
+ public class IncludeTotalCountTests
+ {
+ private static readonly Guid ProjectId = Guid.NewGuid();
+ private static readonly string BaseUrl = $"https://deliver.kontent.ai/{ProjectId}";
+ private static readonly MockHttpMessageHandler MockHttp = new MockHttpMessageHandler();
+ private static readonly DeliveryOptions Options = new DeliveryOptions
+ {
+ ProjectId = ProjectId.ToString(),
+ EnableRetryPolicy = false,
+ IncludeTotalCount = true
+ };
+
+ [Fact]
+ public void PaginationResponse_WithoutTotalCount_DeserializedCorrectly()
+ {
+ var responsePagination = JObject.FromObject(new
+ {
+ skip = 20,
+ limit = 10,
+ count = 8,
+ next_page = "nextPage"
+ });
+ var expected = new Pagination(20, 10, 8, null, "nextPage");
+
+ var result = responsePagination.ToObject();
+
+ result.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void PaginationResponse_WithTotalCount_DeserializedCorrectly()
+ {
+ var responsePagination = JObject.FromObject(new
+ {
+ skip = 20,
+ limit = 10,
+ count = 8,
+ total_count = 28,
+ next_page = "nextPage"
+ });
+ var expected = new Pagination(20, 10, 8, 28, "nextPage");
+
+ var result = responsePagination.ToObject();
+
+ result.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async void GetItemsJson_DeliveryOptionsWithIncludeTotalCount_IncludeTotalCountParameterAdded()
+ {
+ var responseJson = JsonConvert.SerializeObject(CreateItemsResponse());
+ MockHttp
+ .Expect($"{BaseUrl}/items")
+ .WithExactQueryString("includeTotalCount")
+ .Respond("application/json", responseJson);
+ var client = DeliveryClientFactory.GetMockedDeliveryClientWithOptions(Options, MockHttp);
+
+ await client.GetItemsJsonAsync();
+
+ MockHttp.VerifyNoOutstandingExpectation();
+ }
+
+ [Fact]
+ public async void GetItems_DeliveryOptionsWithIncludeTotalCount_IncludeTotalCountParameterAdded()
+ {
+ var responseJson = JsonConvert.SerializeObject(CreateItemsResponse());
+ MockHttp
+ .Expect($"{BaseUrl}/items")
+ .WithExactQueryString("includeTotalCount")
+ .Respond("application/json", responseJson);
+ var client = DeliveryClientFactory.GetMockedDeliveryClientWithOptions(Options, MockHttp);
+
+ await client.GetItemsAsync();
+
+ MockHttp.VerifyNoOutstandingExpectation();
+ }
+
+ [Fact]
+ public async void GetItemsTyped_DeliveryOptionsWithIncludeTotalCount_IncludeTotalCountParameterAdded()
+ {
+ var responseJson = JsonConvert.SerializeObject(CreateItemsResponse());
+ MockHttp
+ .Expect($"{BaseUrl}/items")
+ .WithExactQueryString("system.type=cafe&includeTotalCount")
+ .Respond("application/json", responseJson);
+ var client = DeliveryClientFactory.GetMockedDeliveryClientWithOptions(Options, MockHttp);
+ A.CallTo(() => client.TypeProvider.GetCodename(typeof(Cafe))).Returns("cafe");
+
+ await client.GetItemsAsync();
+
+ MockHttp.VerifyNoOutstandingExpectation();
+ }
+
+ private static object CreateItemsResponse() => new
+ {
+ items = new object[0],
+ modular_content = new { },
+ pagination = new
+ {
+ skip = 0,
+ limit = 0,
+ count = 1,
+ total_count = 1,
+ next_page = ""
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/Kentico.Kontent.Delivery/Builders/DeliveryOptions/DeliveryOptionsBuilder.cs b/Kentico.Kontent.Delivery/Builders/DeliveryOptions/DeliveryOptionsBuilder.cs
index 36794240..d6fb633c 100644
--- a/Kentico.Kontent.Delivery/Builders/DeliveryOptions/DeliveryOptionsBuilder.cs
+++ b/Kentico.Kontent.Delivery/Builders/DeliveryOptions/DeliveryOptionsBuilder.cs
@@ -16,7 +16,7 @@ public class DeliveryOptionsBuilder : IDeliveryApiConfiguration, IDeliveryOption
public static IDeliveryOptionsBuilder CreateInstance()
=> new DeliveryOptionsBuilder();
- private DeliveryOptionsBuilder() {}
+ private DeliveryOptionsBuilder() { }
IDeliveryApiConfiguration IDeliveryOptionsBuilder.WithProjectId(string projectId)
{
@@ -41,6 +41,13 @@ IOptionalDeliveryConfiguration IOptionalDeliveryConfiguration.WaitForLoadingNewC
return this;
}
+ IOptionalDeliveryConfiguration IOptionalDeliveryConfiguration.IncludeTotalCount()
+ {
+ _deliveryOptions.IncludeTotalCount = true;
+
+ return this;
+ }
+
IOptionalDeliveryConfiguration IOptionalDeliveryConfiguration.DisableRetryPolicy()
{
_deliveryOptions.EnableRetryPolicy = false;
diff --git a/Kentico.Kontent.Delivery/Builders/DeliveryOptions/IDeliveryOptionsBuilder.cs b/Kentico.Kontent.Delivery/Builders/DeliveryOptions/IDeliveryOptionsBuilder.cs
index 05aff150..577e3ae3 100644
--- a/Kentico.Kontent.Delivery/Builders/DeliveryOptions/IDeliveryOptionsBuilder.cs
+++ b/Kentico.Kontent.Delivery/Builders/DeliveryOptions/IDeliveryOptionsBuilder.cs
@@ -60,6 +60,13 @@ public interface IOptionalDeliveryConfiguration : IDeliveryOptionsBuild
///
IOptionalDeliveryConfiguration WaitForLoadingNewContent();
+ ///
+ /// Include the total number of items matching the search criteria in the response.
+ /// This behavior can also be enabled for individual requests with the .
+ /// Please note that using this option might increase the response time.
+ ///
+ IOptionalDeliveryConfiguration IncludeTotalCount();
+
///
/// Change configuration of the default retry policy.
///
diff --git a/Kentico.Kontent.Delivery/Configuration/DeliveryOptions .cs b/Kentico.Kontent.Delivery/Configuration/DeliveryOptions .cs
index 14c7cf3d..794c62b9 100644
--- a/Kentico.Kontent.Delivery/Configuration/DeliveryOptions .cs
+++ b/Kentico.Kontent.Delivery/Configuration/DeliveryOptions .cs
@@ -61,5 +61,11 @@ public class DeliveryOptions
/// Gets or sets configuration of the default retry policy.
///
public DefaultRetryPolicyOptions DefaultRetryPolicyOptions { get; set; } = new DefaultRetryPolicyOptions();
+
+ ///
+ /// Gets or sets a value that determines if the client includes the total number of items matching the search criteria in response.
+ /// This behavior can also be enabled for individual requests with the .
+ ///
+ public bool IncludeTotalCount { get; set; }
}
}
diff --git a/Kentico.Kontent.Delivery/DeliveryClient.cs b/Kentico.Kontent.Delivery/DeliveryClient.cs
index 87e7bb76..d588781c 100644
--- a/Kentico.Kontent.Delivery/DeliveryClient.cs
+++ b/Kentico.Kontent.Delivery/DeliveryClient.cs
@@ -207,7 +207,7 @@ public async Task> GetItemsAsync(params IQuery
/// The instance that contains the content items. If no query parameters are specified, all content items are returned.
public async Task> GetItemsAsync(IEnumerable parameters)
{
- var enhancedParameters = ExtractParameters(parameters);
+ var enhancedParameters = EnsureContentTypeFilter(parameters).ToList();
var endpointUrl = UrlBuilder.GetItemsUrl(enhancedParameters);
var response = await GetDeliverResponseAsync(endpointUrl);
@@ -261,7 +261,7 @@ public IDeliveryItemsFeed GetItemsFeed(params IQueryParameter[] parameters
/// The instance that can be used to enumerate through content items. If no query parameters are specified, all content items are enumerated.
public IDeliveryItemsFeed GetItemsFeed(IEnumerable parameters)
{
- var enhancedParameters = ExtractParameters(parameters).ToList();
+ var enhancedParameters = EnsureContentTypeFilter(parameters).ToList();
ValidateItemsFeedParameters(enhancedParameters);
var endpointUrl = UrlBuilder.GetItemsFeedUrl(enhancedParameters);
return new DeliveryItemsFeed(GetItemsBatchAsync);
@@ -555,7 +555,7 @@ private bool HasStaleContent(HttpResponseMessage httpResponseMessage)
return httpResponseMessage.Headers.TryGetValues("X-Stale-Content", out var values) && values.Contains("1", StringComparer.Ordinal);
}
- internal IEnumerable ExtractParameters(IEnumerable parameters = null)
+ internal IEnumerable EnsureContentTypeFilter(IEnumerable parameters = null)
{
var enhancedParameters = parameters != null
? new List(parameters)
diff --git a/Kentico.Kontent.Delivery/DeliveryEndpointUrlBuilder.cs b/Kentico.Kontent.Delivery/DeliveryEndpointUrlBuilder.cs
index 42b2ae96..1429258e 100644
--- a/Kentico.Kontent.Delivery/DeliveryEndpointUrlBuilder.cs
+++ b/Kentico.Kontent.Delivery/DeliveryEndpointUrlBuilder.cs
@@ -35,12 +35,14 @@ public string GetItemUrl(string codename, IEnumerable parameter
public string GetItemsUrl(string[] parameters)
{
- return GetUrl(UrlTemplateItems, parameters);
+ var enrichedParameters = EnrichParameters(parameters).ToArray();
+ return GetUrl(UrlTemplateItems, enrichedParameters);
}
public string GetItemsUrl(IEnumerable parameters)
{
- return GetUrl(UrlTemplateItems, parameters);
+ var updatedParameters = EnrichParameters(parameters);
+ return GetUrl(UrlTemplateItems, updatedParameters);
}
public string GetItemsFeedUrl(string[] parameters)
@@ -138,5 +140,27 @@ private string AssembleHost()
var hostUrl = string.Format(endpointUrlTemplate, projectId);
return hostUrl;
}
+
+ private IEnumerable EnrichParameters(IEnumerable parameters)
+ {
+ var parameterList = parameters?.ToList() ?? new List();
+ if (_deliveryOptions.IncludeTotalCount && !parameterList.Contains(new IncludeTotalCountParameter().GetQueryStringParameter()))
+ {
+ parameterList.Add(new IncludeTotalCountParameter().GetQueryStringParameter());
+ }
+
+ return parameterList;
+ }
+
+ private IEnumerable EnrichParameters(IEnumerable parameters)
+ {
+ var parameterList = parameters?.ToList() ?? new List();
+ if (_deliveryOptions.IncludeTotalCount && !parameterList.Any(x => x is IncludeTotalCountParameter))
+ {
+ parameterList.Add(new IncludeTotalCountParameter());
+ }
+
+ return parameterList;
+ }
}
}
diff --git a/Kentico.Kontent.Delivery/Models/Pagination.cs b/Kentico.Kontent.Delivery/Models/Pagination.cs
index f26b5e58..ae05257a 100644
--- a/Kentico.Kontent.Delivery/Models/Pagination.cs
+++ b/Kentico.Kontent.Delivery/Models/Pagination.cs
@@ -22,6 +22,11 @@ public sealed class Pagination
///
public int Count { get; }
+ ///
+ /// Gets the total number of items matching the search criteria.
+ ///
+ public int? TotalCount { get; }
+
///
/// Gets the URL of the next page.
///
@@ -32,11 +37,12 @@ public sealed class Pagination
/// Initializes a new instance of the class with information from a response.
///
[JsonConstructor]
- internal Pagination(int skip, int limit, int count, string next_page)
+ internal Pagination(int skip, int limit, int count, int? total_count, string next_page)
{
Skip = skip;
Limit = limit;
Count = count;
+ TotalCount = total_count;
NextPageUrl = next_page;
}
}
diff --git a/Kentico.Kontent.Delivery/QueryParameters/Parameters/IncludeTotalCountParameter.cs b/Kentico.Kontent.Delivery/QueryParameters/Parameters/IncludeTotalCountParameter.cs
new file mode 100644
index 00000000..efaefd59
--- /dev/null
+++ b/Kentico.Kontent.Delivery/QueryParameters/Parameters/IncludeTotalCountParameter.cs
@@ -0,0 +1,23 @@
+namespace Kentico.Kontent.Delivery
+{
+ ///
+ /// Specifies whether to include total count in the paging section.
+ ///
+ public sealed class IncludeTotalCountParameter : IQueryParameter
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IncludeTotalCountParameter()
+ {
+ }
+
+ ///
+ /// Returns the query string representation of the query parameter.
+ ///
+ public string GetQueryStringParameter()
+ {
+ return "includeTotalCount";
+ }
+ }
+}
diff --git a/README.md b/README.md
index 2b4ccaba..806a2bee 100644
--- a/README.md
+++ b/README.md
@@ -104,6 +104,21 @@ DeliveryItemListingResponse response = await client.GetItemsAsync(
);
```
+### Paging navigation
+
+To display a paging navigation you need to retrieve the total number of items matching the search criteria. This can be achieved by adding the `IncludeTotalCountParameter` to the request parameters. With this parameter the item listing responses will contain the total number of items in the `Pagination.TotalCount` property. This behavior can also be enabled globally by calling the `IDeliveryOptionsBuilder.IncludeTotalCount` method. Please note that response times might increase slightly.
+
+```csharp
+// Retrieves the second page of items including total number of items matching the search criteria
+DeliveryItemListingResponse response = await client.GetItemsAsync(
+ new LanguageParameter("es-ES"),
+ new EqualsFilter("system.type", "brewer"),
+ new OrderParameter("elements.product_name"),
+ new SkipParameter(5),
+ new LimitParameter(5),
+ new IncludeTotalCountParameter(),
+```
+
### Strongly-typed responses
The `IDeliveryClient` also supports retrieving of strongly-typed models.