diff --git a/CHANGELOG.md b/CHANGELOG.md
index e674e4c..ee2230a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## [v0.6.1](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.6.1) (2026-02-02)
+ - Fix
+ - Release DELETE request no longer includes Content-Type header to comply with API requirements
+
## [v0.6.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.6.0)
- Enhancement
- Refactor retry policy implementation to improve exception handling and retry logic across various scenarios
diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs
index fb4fbce..66f9014 100644
--- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs
+++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs
@@ -970,6 +970,92 @@ public async Task Test020_Should_Delete_Release_Async()
}
}
+ ///
+ /// Verifies that Delete Release API succeeds when the SDK does not send Content-Type header (DELETE /releases/{uid}).
+ /// Creates a release, deletes it without Content-Type, asserts success, then verifies the release is gone.
+ ///
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test021_Should_Delete_Release_Without_Content_Type_Header()
+ {
+ try
+ {
+ var releaseModel = new ReleaseModel
+ {
+ Name = _testReleaseName + " Delete Without Content-Type",
+ Description = _testReleaseDescription + " (Delete without Content-Type header)",
+ Locked = false,
+ Archived = false
+ };
+
+ ContentstackResponse createResponse = _stack.Release().Create(releaseModel);
+ var createResponseJson = createResponse.OpenJObjectResponse();
+ Assert.IsTrue(createResponse.IsSuccessStatusCode, "Create release must succeed.");
+ string releaseToDeleteUid = createResponseJson["release"]["uid"].ToString();
+
+ ContentstackResponse deleteResponse = _stack.Release(releaseToDeleteUid).Delete();
+
+ Assert.IsNotNull(deleteResponse);
+ Assert.IsTrue(deleteResponse.IsSuccessStatusCode, "Delete release (without Content-Type) must succeed.");
+
+ try
+ {
+ var fetchResponse = _stack.Release(releaseToDeleteUid).Fetch();
+ Assert.IsFalse(fetchResponse.IsSuccessStatusCode, "Release must be gone after delete; Fetch should not succeed.");
+ }
+ catch (ContentstackErrorException)
+ {
+ Assert.IsTrue(true, "Release not found after delete (exception path).");
+ }
+ }
+ catch (Exception e)
+ {
+ Assert.Fail($"Delete release without Content-Type header failed: {e.Message}");
+ }
+ }
+ ///
+ /// Verifies that Delete Release API (async) succeeds when the SDK does not send Content-Type header (DELETE /releases/{uid}).
+ /// Creates a release, deletes it without Content-Type, asserts success, then verifies the release is gone.
+ ///
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test022_Should_Delete_Release_Async_Without_Content_Type_Header()
+ {
+ try
+ {
+ var releaseModel = new ReleaseModel
+ {
+ Name = _testReleaseName + " Delete Async Without Content-Type",
+ Description = _testReleaseDescription + " (Delete async without Content-Type header)",
+ Locked = false,
+ Archived = false
+ };
+
+ ContentstackResponse createResponse = await _stack.Release().CreateAsync(releaseModel);
+ var createResponseJson = createResponse.OpenJObjectResponse();
+ Assert.IsTrue(createResponse.IsSuccessStatusCode, "Create release must succeed.");
+ string releaseToDeleteUid = createResponseJson["release"]["uid"].ToString();
+
+ ContentstackResponse deleteResponse = await _stack.Release(releaseToDeleteUid).DeleteAsync();
+
+ Assert.IsNotNull(deleteResponse);
+ Assert.IsTrue(deleteResponse.IsSuccessStatusCode, "Delete release async (without Content-Type) must succeed.");
+
+ try
+ {
+ var fetchResponse = await _stack.Release(releaseToDeleteUid).FetchAsync();
+ Assert.IsFalse(fetchResponse.IsSuccessStatusCode, "Release must be gone after delete; Fetch should not succeed.");
+ }
+ catch (ContentstackErrorException)
+ {
+ Assert.IsTrue(true, "Release not found after delete (exception path).");
+ }
+ }
+ catch (Exception e)
+ {
+ Assert.Fail($"Delete release async without Content-Type header failed: {e.Message}");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Contentstack.Management.Core.Unit.Tests/Core/Services/ContentstackServiceTest.cs b/Contentstack.Management.Core.Unit.Tests/Core/Services/ContentstackServiceTest.cs
index 9b6c28b..873e283 100644
--- a/Contentstack.Management.Core.Unit.Tests/Core/Services/ContentstackServiceTest.cs
+++ b/Contentstack.Management.Core.Unit.Tests/Core/Services/ContentstackServiceTest.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -279,6 +279,19 @@ public void Return_Value_For_HeaderKey()
Assert.AreEqual("application/json", contentstackService.GetHeaderValue(HeadersKey.ContentTypeHeader));
}
+ [TestMethod]
+ public void CreateHttpRequest_Should_Set_Content_Type_When_ShouldSetContentType_Returns_True()
+ {
+ var contentstackService = new ContentstackService(serializer);
+ var config = new ContentstackClientOptions();
+ config.Authtoken = _fixture.Create();
+
+ contentstackService.CreateHttpRequest(new HttpClient(), config);
+
+ Assert.IsTrue(contentstackService.Headers.ContainsKey(HeadersKey.ContentTypeHeader));
+ Assert.AreEqual("application/json", contentstackService.GetHeaderValue(HeadersKey.ContentTypeHeader));
+ }
+
[TestMethod]
public void Return_HttpRequest_On_Create_HttpRequest()
{
diff --git a/Contentstack.Management.Core.Unit.Tests/Core/Services/Models/FetchDeleteServiceTest.cs b/Contentstack.Management.Core.Unit.Tests/Core/Services/Models/FetchDeleteServiceTest.cs
index cf780a2..a822944 100644
--- a/Contentstack.Management.Core.Unit.Tests/Core/Services/Models/FetchDeleteServiceTest.cs
+++ b/Contentstack.Management.Core.Unit.Tests/Core/Services/Models/FetchDeleteServiceTest.cs
@@ -1,9 +1,13 @@
-using System;
+using System;
+using System.Net.Http;
using AutoFixture;
using AutoFixture.AutoMoq;
+using Contentstack.Management.Core;
using Contentstack.Management.Core.Services.Models;
+using Contentstack.Management.Core.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
+
namespace Contentstack.Management.Core.Unit.Tests.Core.Services.Models
{
[TestClass]
@@ -11,8 +15,14 @@ public class FetchDeleteServiceTest
{
private JsonSerializer serializer = JsonSerializer.Create(new JsonSerializerSettings());
private readonly IFixture _fixture = new Fixture()
- .Customize(new AutoMoqCustomization());
+ .Customize(new AutoMoqCustomization());
+ private static ContentstackClientOptions CreateConfig(IFixture fixture)
+ {
+ var config = new ContentstackClientOptions();
+ config.Authtoken = fixture.Create();
+ return config;
+ }
[TestMethod]
public void Should_Throw_On_Null_Serializer()
@@ -60,5 +70,63 @@ public void Should_Provide_Valid_Param_On_Initialize()
Assert.AreEqual("GET", service.HttpMethod);
Assert.AreEqual(resourcePath, service.ResourcePath);
}
+
+ [TestMethod]
+ public void Delete_Release_Should_Not_Include_Content_Type_Header()
+ {
+ var stack = new Management.Core.Models.Stack(null, _fixture.Create());
+ var service = new FetchDeleteService(serializer, stack, "/releases/release_uid_123", "DELETE");
+
+ service.CreateHttpRequest(new HttpClient(), CreateConfig(_fixture));
+
+ Assert.IsFalse(service.Headers.ContainsKey(HeadersKey.ContentTypeHeader), "DELETE /releases/{uid} must not include Content-Type header.");
+ }
+
+ [TestMethod]
+ public void Delete_Release_With_Path_Releases_Only_Should_Not_Include_Content_Type_Header()
+ {
+ var stack = new Management.Core.Models.Stack(null, _fixture.Create());
+ var service = new FetchDeleteService(serializer, stack, "/releases", "DELETE");
+
+ service.CreateHttpRequest(new HttpClient(), CreateConfig(_fixture));
+
+ Assert.IsFalse(service.Headers.ContainsKey(HeadersKey.ContentTypeHeader));
+ }
+
+ [TestMethod]
+ public void Delete_Release_Items_Path_Should_Include_Content_Type_Header()
+ {
+ var stack = new Management.Core.Models.Stack(null, _fixture.Create());
+ var service = new FetchDeleteService(serializer, stack, "/releases/release_uid/item", "DELETE");
+
+ service.CreateHttpRequest(new HttpClient(), CreateConfig(_fixture));
+
+ Assert.IsTrue(service.Headers.ContainsKey(HeadersKey.ContentTypeHeader), "DELETE /releases/{uid}/item (FetchDeleteService) should still set Content-Type.");
+ Assert.AreEqual("application/json", service.GetHeaderValue(HeadersKey.ContentTypeHeader));
+ }
+
+ [TestMethod]
+ public void Fetch_Release_Should_Include_Content_Type_Header()
+ {
+ var stack = new Management.Core.Models.Stack(null, _fixture.Create());
+ var service = new FetchDeleteService(serializer, stack, "/releases/release_uid_123", "GET");
+
+ service.CreateHttpRequest(new HttpClient(), CreateConfig(_fixture));
+
+ Assert.IsTrue(service.Headers.ContainsKey(HeadersKey.ContentTypeHeader));
+ Assert.AreEqual("application/json", service.GetHeaderValue(HeadersKey.ContentTypeHeader));
+ }
+
+ [TestMethod]
+ public void Delete_Non_Release_Resource_Should_Include_Content_Type_Header()
+ {
+ var stack = new Management.Core.Models.Stack(null, _fixture.Create());
+ var service = new FetchDeleteService(serializer, stack, "/contenttypes/ct_uid", "DELETE");
+
+ service.CreateHttpRequest(new HttpClient(), CreateConfig(_fixture));
+
+ Assert.IsTrue(service.Headers.ContainsKey(HeadersKey.ContentTypeHeader));
+ Assert.AreEqual("application/json", service.GetHeaderValue(HeadersKey.ContentTypeHeader));
+ }
}
}
diff --git a/Contentstack.Management.Core.Unit.Tests/Models/ReleaseTest.cs b/Contentstack.Management.Core.Unit.Tests/Models/ReleaseTest.cs
index a764b86..a0e42dc 100644
--- a/Contentstack.Management.Core.Unit.Tests/Models/ReleaseTest.cs
+++ b/Contentstack.Management.Core.Unit.Tests/Models/ReleaseTest.cs
@@ -1,5 +1,6 @@
-using System;
+using System;
using AutoFixture;
+using Contentstack.Management.Core;
using Contentstack.Management.Core.Models;
using Contentstack.Management.Core.Queryable;
using Contentstack.Management.Core.Unit.Tests.Mokes;
diff --git a/Contentstack.Management.Core/Services/ContentstackService.cs b/Contentstack.Management.Core/Services/ContentstackService.cs
index fcf422f..7e56fba 100644
--- a/Contentstack.Management.Core/Services/ContentstackService.cs
+++ b/Contentstack.Management.Core/Services/ContentstackService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Text;
using System.Net.Http;
@@ -151,6 +151,15 @@ public bool HasRequestBody()
return HttpMethod == "POST" || HttpMethod == "PUT" || HttpMethod == "PATCH" || HttpMethod == "DELETE";
}
+ ///
+ /// Returns true if the request should include Content-Type: application/json header.
+ /// Override to skip Content-Type for specific requests (e.g. DELETE /releases).
+ ///
+ protected virtual bool ShouldSetContentType()
+ {
+ return true;
+ }
+
public virtual IHttpRequest CreateHttpRequest(HttpClient httpClient, ContentstackClientOptions config, bool addAcceptMediaHeader = false, string apiVersion = null)
{
ThrowIfDisposed();
@@ -162,7 +171,10 @@ public virtual IHttpRequest CreateHttpRequest(HttpClient httpClient, Contentstac
.Add(new MediaTypeWithQualityHeaderValue("image/jpeg"));
}
Uri requestUri = ContentstackUtilities.ComposeUrI(config.GetUri(), this);
- Headers["Content-Type"] = "application/json";
+ if (ShouldSetContentType())
+ {
+ Headers["Content-Type"] = "application/json";
+ }
if (!string.IsNullOrEmpty(this.ManagementToken))
{
diff --git a/Contentstack.Management.Core/Services/Models/FetchDeleteService.cs b/Contentstack.Management.Core/Services/Models/FetchDeleteService.cs
index 287a408..8bd26bb 100644
--- a/Contentstack.Management.Core/Services/Models/FetchDeleteService.cs
+++ b/Contentstack.Management.Core/Services/Models/FetchDeleteService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Contentstack.Management.Core.Queryable;
using Newtonsoft.Json;
using Contentstack.Management.Core.Utils;
@@ -29,5 +29,19 @@ internal FetchDeleteService(JsonSerializer serializer, Core.Models.Stack stack,
}
}
#endregion
+
+ ///
+ /// Skip Content-Type for DELETE /releases (single release delete). Keep it for DELETE /releases/{uid}/item and other requests.
+ ///
+ protected override bool ShouldSetContentType()
+ {
+ if (HttpMethod != "DELETE" || string.IsNullOrEmpty(ResourcePath))
+ return true;
+ if (!ResourcePath.StartsWith("/releases", StringComparison.Ordinal))
+ return true;
+ if (ResourcePath.EndsWith("/item", StringComparison.Ordinal))
+ return true;
+ return false;
+ }
}
}
diff --git a/Directory.Build.props b/Directory.Build.props
index 5977029..735f780 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,5 @@
- 0.6.0
+ 0.6.1