diff --git a/.talismanrc b/.talismanrc
index 7654d1e..b1596ce 100644
--- a/.talismanrc
+++ b/.talismanrc
@@ -3,13 +3,15 @@ fileignoreconfig:
ignore_detectors:
- filecontent
- filename: Contentstack.Core/Internals/HttpRequestHandler.cs
- checksum: 94288e483056f41ff3a4c2ab652c3ce9ecb53dc0b9d4029456b34baed4f34891
+ checksum: 62053e1b8772f44db054efc504d5d57f28fb7962c81021325854d478f570de09
- filename: Contentstack.Core/Models/Entry.cs
checksum: 78a09b03b9fd6aefd0251353b2d8c70962bdfced16a6e1e28d10dc9af43da244
- filename: Contentstack.Core/ContentstackClient.cs
checksum: 687dc0a5f20037509731cfe540dcec9c3cc2b6cf50373cd183ece4f3249dc88e
- filename: Contentstack.Core/Models/AssetLibrary.cs
- checksum: 7e05fd0bbb43b15e6b7f387d746cc64d709e17e0e8e26a7495a99025077ff507
+ checksum: 0c67f8bb3b7ffdb9b04cd38ae096904b53d6d4372e86c91c1f935e36b6b0ce56
+- filename: Contentstack.Core.Tests/AssetTest.cs
+ checksum: 9e197065aa6ea46af795a8ddb9d652a4972d9d4b4bfc7b1772d304d848f1c3e1
- filename: Contentstack.Core/Models/Asset.cs
checksum: d192718723e6cb2aa8f08f873d3a7ea7268c89cc15da3bdeea4c16fd304c410e
- filename: Contentstack.Core/Models/Query.cs
@@ -20,7 +22,5 @@ fileignoreconfig:
checksum: 53ba4ce874c4d2362ad00deb23f5a6ec219318860352f997b945e9161a580651
- filename: Contentstack.Core.Tests/ContentstackClientTest.cs
checksum: b63897181a8cb5993d1305248cfc3e711c4039b5677b6c1e4e2a639e4ecb391b
-- filename: Contentstack.Core.Tests/AssetTest.cs
- checksum: 3e7bf50c7223c458561f0217484d5e70cf3770490c569e0a7083b0a12af9ab86
- filename: Contentstack.Core.Tests/RegionHandlerTest.cs
checksum: 69899138754908e156aa477d775d12fd6b3fefc1a6c2afec22cb409bd6e6446c
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 171471c..b335753 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+### Version: 2.25.0
+#### Date: Jan-07-2025
+
+##### Feat:
+- AssetLibrary
+ - Added new `Where` method for simple key-value pair queries
+ - Enhanced `Query` method to support multiple calls with intelligent merging
+- Improved query handling with better null safety and error handling
+
### Version: 2.24.0
#### Date: Sep-29-2025
diff --git a/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj b/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj
index 4d960fc..b71bafa 100644
--- a/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj
+++ b/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj
@@ -8,7 +8,7 @@
Contentstack
$(Version)
Main release
- Copyright (c) 2012-2024 Contentstack (http://app.contentstack.com). All Rights Reserved
+ Copyright (c) 2012-2025 Contentstack (http://app.contentstack.com). All Rights Reserved
https://github.com/contentstack/contentstack-dotnet
v$(Version)
$(Version)
diff --git a/Contentstack.Core.Tests/AssetTest.cs b/Contentstack.Core.Tests/AssetTest.cs
index 25f4fd7..e716901 100644
--- a/Contentstack.Core.Tests/AssetTest.cs
+++ b/Contentstack.Core.Tests/AssetTest.cs
@@ -661,5 +661,295 @@ public async Task AssetTags_CaseSensitivityVerification_ShouldTestCaseBehavior_T
}
}
}
+
+ [Fact]
+ public void Query_MultipleCalls_ShouldMergeQueries_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject firstQuery = new JObject
+ {
+ { "filename", "test1.png" },
+ { "content_type", "image/png" }
+ };
+ JObject secondQuery = new JObject
+ {
+ { "file_size", 1024 },
+ { "tags", new JArray { "test", "image" } }
+ };
+
+ // Act
+ var result = assetLibrary.Query(firstQuery).Query(secondQuery);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ // The method should not throw an exception when called multiple times
+ }
+
+ [Fact]
+ public void Query_SingleCall_ShouldWorkAsBefore_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject queryObject = new JObject
+ {
+ { "filename", "test.png" }
+ };
+
+ // Act
+ var result = assetLibrary.Query(queryObject);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Query_WithEmptyObject_ShouldNotThrowException_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject emptyQuery = new JObject();
+
+ // Act & Assert
+ var result = assetLibrary.Query(emptyQuery);
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Query_WithNullValues_ShouldHandleGracefully_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject queryWithNulls = new JObject
+ {
+ { "filename", "test.png" },
+ { "null_field", null }
+ };
+
+ // Act & Assert
+ var result = assetLibrary.Query(queryWithNulls);
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Query_ChainedWithOtherMethods_ShouldWork_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject queryObject = new JObject
+ {
+ { "filename", "test.png" }
+ };
+
+ // Act
+ var result = assetLibrary
+ .Query(queryObject)
+ .Limit(10)
+ .Skip(0)
+ .IncludeMetadata();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Query_MultipleCallsWithSameKeys_ShouldMergeValues_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject firstQuery = new JObject
+ {
+ { "tags", new JArray { "tag1", "tag2" } }
+ };
+ JObject secondQuery = new JObject
+ {
+ { "tags", new JArray { "tag3", "tag4" } }
+ };
+
+ // Act
+ var result = assetLibrary.Query(firstQuery).Query(secondQuery);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ // The method should handle merging arrays without throwing exceptions
+ }
+
+ [Fact]
+ public void Query_WithComplexNestedObjects_ShouldMergeCorrectly_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject firstQuery = new JObject
+ {
+ { "metadata", new JObject
+ {
+ { "author", "John Doe" },
+ { "version", 1 }
+ }
+ }
+ };
+ JObject secondQuery = new JObject
+ {
+ { "metadata", new JObject
+ {
+ { "department", "IT" }
+ }
+ },
+ { "filename", "test.png" }
+ };
+
+ // Act
+ var result = assetLibrary.Query(firstQuery).Query(secondQuery);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_SingleCall_ShouldAddKeyValuePair_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ string key = "filename";
+ string value = "test.png";
+
+ // Act
+ var result = assetLibrary.Where(key, value);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_MultipleCalls_ShouldAddMultipleKeyValuePairs_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+
+ // Act
+ var result = assetLibrary
+ .Where("filename", "test.png")
+ .Where("content_type", "image/png")
+ .Where("file_size", "1024");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_WithEmptyStrings_ShouldHandleGracefully_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+
+ // Act & Assert
+ var result = assetLibrary.Where("", "");
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_WithNullKey_ShouldHandleGracefully_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+
+ // Act & Assert
+ var result = assetLibrary.Where(null, "value");
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_WithNullValue_ShouldHandleGracefully_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+
+ // Act & Assert
+ var result = assetLibrary.Where("key", null);
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_ChainedWithOtherMethods_ShouldWork_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+
+ // Act
+ var result = assetLibrary
+ .Where("filename", "test.png")
+ .Limit(10)
+ .Skip(0)
+ .IncludeMetadata();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_WithQueryMethod_ShouldWorkTogether_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+ JObject queryObject = new JObject
+ {
+ { "content_type", "image/png" }
+ };
+
+ // Act
+ var result = assetLibrary
+ .Query(queryObject)
+ .Where("filename", "test.png")
+ .Where("file_size", "1024");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_OverwritesExistingKey_ShouldReplaceValue_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+
+ // Act
+ var result = assetLibrary
+ .Where("filename", "original.png")
+ .Where("filename", "updated.png");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Where_WithSpecialCharacters_ShouldHandleCorrectly_Test()
+ {
+ // Arrange
+ AssetLibrary assetLibrary = client.AssetLibrary();
+
+ // Act
+ var result = assetLibrary
+ .Where("file_name", "test-file_123.png")
+ .Where("description", "File with special chars: @#$%");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
}
}
\ No newline at end of file
diff --git a/Contentstack.Core.Tests/VersionUtilityTest.cs b/Contentstack.Core.Tests/VersionUtilityTest.cs
new file mode 100644
index 0000000..8811807
--- /dev/null
+++ b/Contentstack.Core.Tests/VersionUtilityTest.cs
@@ -0,0 +1,379 @@
+using System;
+using System.Reflection;
+using Contentstack.Core.Internals;
+using Xunit;
+
+namespace Contentstack.Core.Tests
+{
+ public class VersionUtilityTest
+ {
+ #region GetSdkVersion Tests
+
+ [Fact]
+ public void GetSdkVersion_ReturnsValidFormat()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.StartsWith("contentstack-delivery-dotnet/", version);
+ Assert.True(version.Length > "contentstack-delivery-dotnet/".Length);
+ }
+
+ [Fact]
+ public void GetSdkVersion_ReturnsConsistentResult()
+ {
+ // Act
+ var version1 = VersionUtility.GetSdkVersion();
+ var version2 = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.Equal(version1, version2);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotReturnNull()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.NotEmpty(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotReturnEmptyString()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotEmpty(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_ContainsExpectedPrefix()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.StartsWith("contentstack-delivery-dotnet/", version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotContainSpaces()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.DoesNotContain(" ", version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotContainNewlines()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.DoesNotContain("\n", version);
+ Assert.DoesNotContain("\r", version);
+ }
+
+ #endregion
+
+ #region ExtractSemanticVersion Tests (via Reflection)
+
+ [Theory]
+ [InlineData("1.2.3", "1.2.3")]
+ [InlineData("1.2.3-beta.1", "1.2.3-beta")]
+ [InlineData("1.2.3+abc123", "1.2.3")]
+ [InlineData("1.2.3-beta.1+abc123", "1.2.3-beta")]
+ [InlineData("2.25.0", "2.25.0")]
+ [InlineData("2.25.0-beta.1", "2.25.0-beta")]
+ [InlineData("2.25.0+abc123", "2.25.0")]
+ [InlineData("2.25.0-beta.1+abc123", "2.25.0-beta")]
+ [InlineData("10.20.30", "10.20.30")]
+ [InlineData("0.1.0", "0.1.0")]
+ public void ExtractSemanticVersion_ValidInputs_ReturnsCorrectVersion(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2")]
+ [InlineData("1")]
+ [InlineData("")]
+ [InlineData("invalid")]
+ [InlineData(" ")]
+ [InlineData("\t")]
+ [InlineData("\n")]
+ [InlineData("\r\n")]
+ public void ExtractSemanticVersion_InvalidInputs_ReturnsNull(string input)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ExtractSemanticVersion_NullInput_ReturnsNull()
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { null }) as string;
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3+", "1.2.3")] // Should handle trailing + correctly
+ [InlineData(" 1.2.3 ", "1.2.3")] // Should handle whitespace
+ [InlineData("1.2.3.4.5", "1.2.3")] // Should handle extra version parts
+ public void ExtractSemanticVersion_ImprovedHandling_WorksCorrectly(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3.4", "1.2.3")] // Should take only first 3 parts
+ [InlineData("1.2.3.4.5.6", "1.2.3")] // Should take only first 3 parts
+ [InlineData("1.2.3.4.5", "1.2.3")] // Should take only first 3 parts
+ public void ExtractSemanticVersion_MoreThanThreeParts_TakesFirstThree(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3+metadata", "1.2.3")]
+ [InlineData("1.2.3-beta.1+metadata", "1.2.3-beta")]
+ [InlineData("1.2.3+very-long-metadata-string", "1.2.3")]
+ [InlineData("1.2.3+", "1.2.3")]
+ public void ExtractSemanticVersion_WithBuildMetadata_RemovesMetadata(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3-beta.1", "1.2.3-beta")]
+ [InlineData("1.2.3-alpha.1", "1.2.3-alpha")]
+ [InlineData("1.2.3-rc.1", "1.2.3-rc")]
+ [InlineData("1.2.3-preview.1", "1.2.3-preview")]
+ public void ExtractSemanticVersion_WithPreReleaseIdentifiers_KeepsPreRelease(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ #endregion
+
+ #region Edge Cases and Error Scenarios
+
+ [Fact]
+ public void GetSdkVersion_HandlesExceptions_Gracefully()
+ {
+ // This test ensures that GetSdkVersion doesn't throw exceptions
+ // and returns a fallback value when assembly reflection fails
+
+ // Act & Assert - should not throw
+ var version = VersionUtility.GetSdkVersion();
+ Assert.NotNull(version);
+ Assert.NotEmpty(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_ReturnsFallbackWhenAssemblyVersionIsInvalid()
+ {
+ // This test verifies that when assembly version is 0.0.0.0 or invalid,
+ // the method falls back to other version sources or returns "dev"
+
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.True(version == "contentstack-delivery-dotnet/dev" ||
+ version.StartsWith("contentstack-delivery-dotnet/"));
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(" ")]
+ [InlineData("\t")]
+ [InlineData("\n")]
+ [InlineData("\r\n")]
+ public void ExtractSemanticVersion_WhitespaceInputs_ReturnsNull(string input)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3.4.5.6.7.8.9.10")] // Very long version
+ [InlineData("999999999.999999999.999999999")] // Very large numbers
+ [InlineData("0.0.0")] // All zeros
+ public void ExtractSemanticVersion_EdgeCaseInputs_HandlesCorrectly(string input)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ if (input == "0.0.0")
+ {
+ Assert.Equal("0.0.0", result);
+ }
+ else
+ {
+ Assert.NotNull(result);
+ Assert.True(result.Split('.').Length == 3);
+ }
+ }
+
+ [Fact]
+ public void ExtractSemanticVersion_HandlesExceptions_Gracefully()
+ {
+ // This test ensures that ExtractSemanticVersion doesn't throw exceptions
+ // and returns null when parsing fails
+
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act & Assert - should not throw
+ var result = method.Invoke(null, new object[] { "invalid-version-string" }) as string;
+ Assert.Null(result);
+ }
+
+ #endregion
+
+ #region Integration Tests
+
+ [Fact]
+ public void GetSdkVersion_Integration_ReturnsValidUserAgentFormat()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.StartsWith("contentstack-delivery-dotnet/", version);
+
+ // Verify it's in a format suitable for User-Agent headers
+ Assert.DoesNotContain(" ", version);
+ Assert.DoesNotContain("\n", version);
+ Assert.DoesNotContain("\r", version);
+ Assert.DoesNotContain("\t", version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_Integration_CanBeUsedInHttpHeaders()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ // Verify the version string is suitable for HTTP headers
+ Assert.NotNull(version);
+ Assert.NotEmpty(version);
+
+ // Should not contain characters that would break HTTP headers
+ Assert.DoesNotContain("\"", version);
+ Assert.DoesNotContain("'", version);
+ Assert.DoesNotContain("\n", version);
+ Assert.DoesNotContain("\r", version);
+ }
+
+ #endregion
+
+ #region Performance Tests
+
+ [Fact]
+ public void GetSdkVersion_Performance_ReturnsQuickly()
+ {
+ // Act & Assert
+ var startTime = DateTime.UtcNow;
+ var version = VersionUtility.GetSdkVersion();
+ var endTime = DateTime.UtcNow;
+
+ // Should complete quickly (less than 1 second)
+ var duration = endTime - startTime;
+ Assert.True(duration.TotalSeconds < 1, $"GetSdkVersion took {duration.TotalSeconds} seconds");
+ Assert.NotNull(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_Performance_MultipleCalls_Consistent()
+ {
+ // Act
+ var versions = new string[100];
+ for (int i = 0; i < 100; i++)
+ {
+ versions[i] = VersionUtility.GetSdkVersion();
+ }
+
+ // Assert
+ var firstVersion = versions[0];
+ for (int i = 1; i < versions.Length; i++)
+ {
+ Assert.Equal(firstVersion, versions[i]);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Contentstack.Core/Contentstack.Core.csproj b/Contentstack.Core/Contentstack.Core.csproj
index 63bf128..2ac53aa 100644
--- a/Contentstack.Core/Contentstack.Core.csproj
+++ b/Contentstack.Core/Contentstack.Core.csproj
@@ -8,7 +8,7 @@
$(Version)
Contentstack
Reference in entry Live preview support added
- Copyright © 2012-2024 Contentstack. All Rights Reserved
+ Copyright © 2012-2025 Contentstack. All Rights Reserved
true
v$(Version)
https://github.com/contentstack/contentstack-dotnet
@@ -54,4 +54,9 @@
+
+
+ <_Parameter1>Contentstack.Core.Tests
+
+
diff --git a/Contentstack.Core/Internals/HttpRequestHandler.cs b/Contentstack.Core/Internals/HttpRequestHandler.cs
index 85f8a10..8d36749 100644
--- a/Contentstack.Core/Internals/HttpRequestHandler.cs
+++ b/Contentstack.Core/Internals/HttpRequestHandler.cs
@@ -12,43 +12,56 @@ namespace Contentstack.Core.Internals
{
internal class HttpRequestHandler
{
- ContentstackClient client
- {
- get; set;
- }
+ ContentstackClient client { get; set; }
+
internal HttpRequestHandler(ContentstackClient contentstackClient)
{
client = contentstackClient;
}
- public async Task ProcessRequest(string Url, Dictionary Headers, Dictionary BodyJson, string FileName = null, string Branch = null, bool isLivePreview = false, int timeout = 30000, WebProxy proxy = null)
- {
- String queryParam = String.Join("&", BodyJson.Select(kvp => {
- var value = "";
- if (kvp.Value is string[])
+ public async Task ProcessRequest(
+ string Url,
+ Dictionary Headers,
+ Dictionary BodyJson,
+ string FileName = null,
+ string Branch = null,
+ bool isLivePreview = false,
+ int timeout = 30000,
+ WebProxy proxy = null
+ )
+ {
+ String queryParam = String.Join(
+ "&",
+ BodyJson.Select(kvp =>
{
- string[] vals = (string[])kvp.Value;
- value = String.Join("&", vals.Select(item =>
+ var value = "";
+ if (kvp.Value is string[])
{
- return String.Format("{0}={1}", kvp.Key, item);
- }));
- return value;
- }
- else if (kvp.Value is Dictionary)
- value = JsonConvert.SerializeObject(kvp.Value);
- else
- return String.Format("{0}={1}", kvp.Key, kvp.Value);
-
- return String.Format("{0}={1}", kvp.Key, value);
+ string[] vals = (string[])kvp.Value;
+ value = String.Join(
+ "&",
+ vals.Select(item =>
+ {
+ return String.Format("{0}={1}", kvp.Key, item);
+ })
+ );
+ return value;
+ }
+ else if (kvp.Value is Dictionary)
+ value = JsonConvert.SerializeObject(kvp.Value);
+ else
+ return String.Format("{0}={1}", kvp.Key, kvp.Value);
- }));
+ return String.Format("{0}={1}", kvp.Key, value);
+ })
+ );
- var uri = new Uri(Url+"?"+queryParam);
+ var uri = new Uri(Url + "?" + queryParam);
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "GET";
request.ContentType = "application/json";
- request.Headers["x-user-agent"]="contentstack-delivery-dotnet/2.24.0";
+ request.Headers["x-user-agent"] = VersionUtility.GetSdkVersion();
request.Timeout = timeout;
if (proxy != null)
@@ -60,52 +73,68 @@ public async Task ProcessRequest(string Url, Dictionary
{
request.Headers["branch"] = Branch;
}
- if (Headers != default(IDictionary)) {
- foreach (var header in Headers) {
- try {
+ if (Headers != default(IDictionary))
+ {
+ foreach (var header in Headers)
+ {
+ try
+ {
request.Headers[header.Key] = header.Value.ToString();
- } catch {
-
}
+ catch { }
}
}
foreach (var plugin in client.Plugins)
{
request = await plugin.OnRequest(client, request);
- };
+ }
+ ;
var serializedresult = JsonConvert.SerializeObject(BodyJson);
byte[] requestBody = Encoding.UTF8.GetBytes(serializedresult);
StreamReader reader = null;
HttpWebResponse response = null;
- try {
+ try
+ {
response = (HttpWebResponse)await request.GetResponseAsync();
- if (response != null) {
+ if (response != null)
+ {
reader = new StreamReader(response.GetResponseStream());
string responseString = await reader.ReadToEndAsync();
foreach (var plugin in client.Plugins)
{
- responseString = await plugin.OnResponse(client, request, response, responseString);
+ responseString = await plugin.OnResponse(
+ client,
+ request,
+ response,
+ responseString
+ );
}
return responseString;
- } else {
+ }
+ else
+ {
return null;
}
- } catch (Exception we) {
+ }
+ catch (Exception we)
+ {
throw we;
- } finally {
- if (reader != null) {
+ }
+ finally
+ {
+ if (reader != null)
+ {
reader.Dispose();
}
if (response != null)
{
- response.Dispose();
+ response.Dispose();
}
}
-
}
//internal void updateLivePreviewContent(JObject response)
diff --git a/Contentstack.Core/Internals/VersionUtility.cs b/Contentstack.Core/Internals/VersionUtility.cs
new file mode 100644
index 0000000..16d8172
--- /dev/null
+++ b/Contentstack.Core/Internals/VersionUtility.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Reflection;
+
+namespace Contentstack.Core.Internals
+{
+ ///
+ /// Utility class for handling SDK version information and user-agent string generation.
+ ///
+ internal static class VersionUtility
+ {
+ ///
+ /// Gets the SDK version dynamically from the assembly.
+ ///
+ /// The SDK version string in format: contentstack-delivery-dotnet/Major.Minor.Patch
+ public static string GetSdkVersion()
+ {
+ try
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var version = assembly.GetName().Version;
+
+ // Check if version is valid (not 0.0.0.0 which is default for unversioned assemblies)
+ if (
+ version != null
+ && (version.Major > 0 || version.Minor > 0 || version.Build > 0)
+ )
+ {
+ return $"contentstack-delivery-dotnet/{version.Major}.{version.Minor}.{version.Build}";
+ }
+
+ // Try to get version from assembly file version as fallback
+ var fileVersion = assembly
+ .GetCustomAttribute()
+ ?.Version;
+ if (!string.IsNullOrEmpty(fileVersion))
+ {
+ // Parse file version and extract only Major.Minor.Build (first 3 parts)
+ var versionParts = fileVersion.Split('.');
+ if (versionParts.Length >= 3)
+ {
+ return $"contentstack-delivery-dotnet/{versionParts[0]}.{versionParts[1]}.{versionParts[2]}";
+ }
+ return $"contentstack-delivery-dotnet/{fileVersion}";
+ }
+
+ // Try to get version from assembly informational version
+ var infoVersion = assembly
+ .GetCustomAttribute()
+ ?.InformationalVersion;
+ if (!string.IsNullOrEmpty(infoVersion))
+ {
+ // Extract semantic version (Major.Minor.Patch) from informational version
+ // Handle formats like "2.25.0", "2.25.0-beta.1", "2.25.0+abc123"
+ var semanticVersion = ExtractSemanticVersion(infoVersion);
+ if (!string.IsNullOrEmpty(semanticVersion))
+ {
+ return $"contentstack-delivery-dotnet/{semanticVersion}";
+ }
+ return $"contentstack-delivery-dotnet/{infoVersion}";
+ }
+ }
+ catch
+ {
+ // Ignore exceptions and continue to fallback
+ }
+
+ // Final fallback - use a generic identifier that doesn't imply a specific version
+ return "contentstack-delivery-dotnet/dev";
+ }
+
+ ///
+ /// Extracts semantic version (Major.Minor.Patch) from informational version string.
+ ///
+ /// The informational version string.
+ /// Semantic version in Major.Minor.Patch format.
+ private static string ExtractSemanticVersion(string informationalVersion)
+ {
+ try
+ {
+ // Handle null or empty input
+ if (string.IsNullOrWhiteSpace(informationalVersion))
+ {
+ return null;
+ }
+
+ // Remove build metadata (everything after +)
+ var versionWithoutMetadata = informationalVersion.Split('+')[0];
+
+ // Handle case where version ends with + (e.g., "1.2.3+")
+ if (string.IsNullOrWhiteSpace(versionWithoutMetadata))
+ {
+ return null;
+ }
+
+ // Split by dots to get version parts
+ var parts = versionWithoutMetadata.Split('.');
+
+ // Ensure we have at least 3 parts (Major.Minor.Patch)
+ if (parts.Length >= 3)
+ {
+ // Validate that all parts are numeric or contain valid version identifiers
+ var major = parts[0].Trim();
+ var minor = parts[1].Trim();
+ var patch = parts[2].Trim();
+
+ // Check if we have valid version components
+ if (
+ !string.IsNullOrEmpty(major)
+ && !string.IsNullOrEmpty(minor)
+ && !string.IsNullOrEmpty(patch)
+ )
+ {
+ // Take only the first 3 parts (Major.Minor.Patch)
+ return $"{major}.{minor}.{patch}";
+ }
+ }
+
+ return null;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/Contentstack.Core/LICENSE.txt b/Contentstack.Core/LICENSE.txt
index ec1403b..e1238b5 100644
--- a/Contentstack.Core/LICENSE.txt
+++ b/Contentstack.Core/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2012-2024 Contentstack (http://app.contentstack.com). All Rights Reserved
+Copyright (c) 2012-2025 Contentstack (http://app.contentstack.com). All Rights Reserved
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Contentstack.Core/Models/AssetLibrary.cs b/Contentstack.Core/Models/AssetLibrary.cs
index 8ec17e8..cc7643c 100644
--- a/Contentstack.Core/Models/AssetLibrary.cs
+++ b/Contentstack.Core/Models/AssetLibrary.cs
@@ -12,8 +12,6 @@ namespace Contentstack.Core.Models
{
public class AssetLibrary
{
-
-
#region Internal Variables
private Dictionary _ObjectAttributes = new Dictionary();
private Dictionary _Headers = new Dictionary();
@@ -30,17 +28,12 @@ private string _Url
}
#endregion
- public ContentstackClient Stack
- {
- get;
- set;
- }
+ public ContentstackClient Stack { get; set; }
#region Internal Constructors
- internal AssetLibrary()
- {
- }
+ internal AssetLibrary() { }
+
internal AssetLibrary(ContentstackClient stack)
{
this.Stack = stack;
@@ -74,13 +67,12 @@ public void SortWithKeyAndOrderBy(String key, OrderBy order)
UrlQueries.Add("desc", key);
}
}
+
///
/// Provides only the number of assets.
///
///
///
-
-
/// ContentstackClient stack = new ContentstackClinet("api_key", "delivery_token", "environment");
/// AssetLibrary assetLibrary = stack.AssetLibrary();
/// JObject jObject = await assetLibrary.Count();
@@ -96,7 +88,30 @@ public AssetLibrary Query(JObject QueryObject)
{
try
{
- UrlQueries.Add("query", QueryObject);
+ if (UrlQueries.ContainsKey("query"))
+ {
+ // If query already exists, append/merge the new query object
+ JObject existingQuery = UrlQueries["query"] as JObject;
+ if (existingQuery != null)
+ {
+ // Merge the new query object with the existing one
+ existingQuery.Merge(QueryObject, new JsonMergeSettings
+ {
+ MergeArrayHandling = MergeArrayHandling.Union
+ });
+ UrlQueries["query"] = existingQuery;
+ }
+ else
+ {
+ // If existing query is not a JObject, replace it
+ UrlQueries["query"] = QueryObject;
+ }
+ }
+ else
+ {
+ // If query doesn't exist, add it
+ UrlQueries.Add("query", QueryObject);
+ }
}
catch (Exception e)
{
@@ -104,6 +119,63 @@ public AssetLibrary Query(JObject QueryObject)
}
return this;
}
+
+ ///
+ /// Adds a key-value pair to the query object in UrlQueries.
+ ///
+ /// The key to add to the query object.
+ /// The value to add to the query object.
+ /// Current instance of AssetLibrary, this will be useful for a chaining calls.
+ ///
+ ///
+ /// ContentstackClient stack = new ContentstackClinet("api_key", "delivery_token", "environment");
+ /// AssetLibrary assetLibrary = stack.AssetLibrary();
+ /// assetLibrary.Where("filename", "image.png");
+ /// ContentstackCollection contentstackCollection = await assetLibrary.FetchAll();
+ ///
+ ///
+ public AssetLibrary Where(string key, string value)
+ {
+ try
+ {
+ // Handle null or empty key gracefully
+ if (string.IsNullOrEmpty(key))
+ {
+ return this;
+ }
+
+ if (UrlQueries.ContainsKey("query"))
+ {
+ // If query already exists, get it and add the key-value pair
+ JObject existingQuery = UrlQueries["query"] as JObject;
+ if (existingQuery != null)
+ {
+ existingQuery[key] = value;
+ UrlQueries["query"] = existingQuery;
+ }
+ else
+ {
+ // If existing query is not a JObject, create a new one
+ JObject newQuery = new JObject();
+ newQuery[key] = value;
+ UrlQueries["query"] = newQuery;
+ }
+ }
+ else
+ {
+ // If query doesn't exist, create a new one with the key-value pair
+ JObject newQuery = new JObject();
+ newQuery[key] = value;
+ UrlQueries.Add("query", newQuery);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new Exception(StackConstants.ErrorMessage_QueryFilterException, e);
+ }
+ return this;
+ }
+
///
/// Include fallback locale publish content, if specified locale content is not publish.
///
@@ -154,7 +226,6 @@ public AssetLibrary IncludeBranch()
return this;
}
-
///
/// Add param in URL query.
///
@@ -167,7 +238,6 @@ public AssetLibrary IncludeBranch()
/// ContentstackCollection contentstackCollection = await assetLibrary.FetchAll();
///
///
- /// Where function
public AssetLibrary AddParam(string key, string value)
{
UrlQueries.Add(key, value);
@@ -218,9 +288,6 @@ public void IncludeCount()
UrlQueries.Add("include_count", "true");
}
-
-
-
///
/// This call includes metadata in the response.
///
@@ -246,8 +313,6 @@ public AssetLibrary IncludeMetadata()
return this;
}
-
-
///
/// This method includes the relative url of assets.
///
@@ -465,7 +530,8 @@ public AssetLibrary RemoveHeader(string key)
public async Task> FetchAll()
{
JObject json = await Exec();
- var assets = json.SelectToken("$.assets").ToObject>(this.Stack.Serializer);
+ var assets = json.SelectToken("$.assets")
+ .ToObject>(this.Stack.Serializer);
var collection = json.ToObject>(this.Stack.Serializer);
foreach (var entry in assets)
{
@@ -502,9 +568,15 @@ private async Task Exec()
try
{
HttpRequestHandler RequestHandler = new HttpRequestHandler(this.Stack);
- var outputResult = await RequestHandler.ProcessRequest(_Url, headers, mainJson, Branch: this.Stack.Config.Branch, timeout: this.Stack.Config.Timeout, proxy: this.Stack.Config.Proxy);
+ var outputResult = await RequestHandler.ProcessRequest(
+ _Url,
+ headers,
+ mainJson,
+ Branch: this.Stack.Config.Branch,
+ timeout: this.Stack.Config.Timeout,
+ proxy: this.Stack.Config.Proxy
+ );
return JObject.Parse(ContentstackConvert.ToString(outputResult, "{}"));
-
}
catch (Exception ex)
{
@@ -540,19 +612,18 @@ private Dictionary GetHeader(Dictionary localHea
}
return classHeaders;
-
}
else
{
return localHeader;
}
-
}
else
{
return _StackHeaders;
}
}
+
internal static ContentstackException GetContentstackError(Exception ex)
{
Int32 errorCode = 0;
@@ -599,7 +670,7 @@ internal static ContentstackException GetContentstackError(Exception ex)
ErrorCode = errorCode,
ErrorMessage = errorMessage,
StatusCode = statusCode,
- Errors = errors
+ Errors = errors,
};
return contentstackError;
diff --git a/Directory.Build.props b/Directory.Build.props
index 2263977..3645501 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,5 @@
- 2.24.0
+ 2.25.0