From 955b15fcbe0cdfded41f77bc498c48f55875695d Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 13 Oct 2026 18:32:48 +0500 Subject: [PATCH] http: allow assigning null to HtmxResponseHeaders properties --- .../HtmxResponseHeaders.cs | 76 ++++++++++++------- .../HtmxResponseHeadersTests.cs | 2 +- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs b/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs index e0ad00d..f556a5b 100644 --- a/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs +++ b/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Http; using Ramstack.HtmxToolkit.Internal; @@ -25,9 +24,11 @@ internal HtmxResponseHeaders(HttpResponse response) => /// Gets or sets the value of the HX-Location header, which performs /// a client-side redirect without a full-page reload. /// - /// Assigning or an empty string removes the header. - [MaybeNull] - public string Location + /// + /// If the header is not present, the property returns . + /// Assigning or an empty string removes the header. + /// + public string? Location { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.Location); set => SetHeader(_response.Headers, HtmxResponseHeaderNames.Location, value); @@ -38,14 +39,16 @@ public string Location /// onto the browser's history stack. /// /// - /// Assigning or an empty string removes the header. /// - /// Unlike , setting this property - /// does not remove the HX-Replace-Url header. + /// If the header is not present, the property returns . + /// Assigning or an empty string removes the header. + /// + /// + /// Unlike , setting this property does not remove + /// the HX-Replace-Url header or the legacy HX-Push header. /// /// - [MaybeNull] - public string PushUrl + public string? PushUrl { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.PushUrl); set => SetHeader(_response.Headers, HtmxResponseHeaderNames.PushUrl, value); @@ -56,14 +59,16 @@ public string PushUrl /// without pushing a new entry to the browser's history stack. /// /// - /// Assigning or an empty string removes the header. /// - /// Unlike , setting this property - /// does not remove the HX-Push-Url header. + /// If the header is not present, the property returns . + /// Assigning or an empty string removes the header. + /// + /// + /// Unlike , setting this property does not remove + /// the HX-Push-Url header or the legacy HX-Push header. /// /// - [MaybeNull] - public string ReplaceUrl + public string? ReplaceUrl { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.ReplaceUrl); set => SetHeader(_response.Headers, HtmxResponseHeaderNames.ReplaceUrl, value); @@ -73,9 +78,11 @@ public string ReplaceUrl /// Gets or sets the value of the HX-Redirect header, which performs /// a client-side redirect with a full-page reload. /// - /// Assigning or an empty string removes the header. - [MaybeNull] - public string Redirect + /// + /// If the header is not present, the property returns . + /// Assigning or an empty string removes the header. + /// + public string? Redirect { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.Redirect); set => SetHeader(_response.Headers, HtmxResponseHeaderNames.Redirect, value); @@ -84,6 +91,7 @@ public string Redirect /// /// Gets or sets a value indicating whether the HX-Refresh header requests a full-page refresh. /// + /// Assigning removes the header. public bool Refresh { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.Refresh) == "true"; @@ -93,19 +101,25 @@ public bool Refresh /// /// Gets or sets the swap style specified by the HX-Reswap header. /// - [DisallowNull] + /// + /// Reads the style from , or returns + /// if the header is not present or the style is not recognized. Assigning a style replaces + /// the complete header value, including any modifiers. Assigning removes the header. + /// public HtmxSwap? Reswap { get => EnumHelper.ParseHtmxSwap(GetHeader(_response.Headers, HtmxResponseHeaderNames.Reswap)); - set => SetHeader(_response.Headers, HtmxResponseHeaderNames.Reswap, value.GetValueOrDefault().GetSwapValue()); + set => SetHeader(_response.Headers, HtmxResponseHeaderNames.Reswap, value?.GetSwapValue()); } /// /// Gets or sets the complete HX-Reswap header value, including any swap modifiers. /// - /// Assigning or an empty string removes the header. - [MaybeNull] - public string ReswapExpression + /// + /// If the header is not present, the property returns . + /// Assigning or an empty string removes the header. + /// + public string? ReswapExpression { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.Reswap); set => SetHeader(_response.Headers, HtmxResponseHeaderNames.Reswap, value); @@ -115,9 +129,11 @@ public string ReswapExpression /// Gets or sets the CSS selector specified by the HX-Retarget header /// to change the target of the content update. /// - /// Assigning or an empty string removes the header. - [MaybeNull] - public string Retarget + /// + /// If the header is not present, the property returns . + /// Assigning or an empty string removes the header. + /// + public string? Retarget { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.Retarget); set => SetHeader(_response.Headers, HtmxResponseHeaderNames.Retarget, value); @@ -127,9 +143,11 @@ public string Retarget /// Gets or sets the CSS selector specified by the HX-Reselect header /// to determine which part of the response is swapped in. /// - /// Assigning or an empty string removes the header. - [MaybeNull] - public string Reselect + /// + /// If the header is not present, the property returns . + /// Assigning or an empty string removes the header. + /// + public string? Reselect { get => GetHeader(_response.Headers, HtmxResponseHeaderNames.Reselect); set => SetHeader(_response.Headers, HtmxResponseHeaderNames.Reselect, value); @@ -147,7 +165,7 @@ public string Reselect /// the property returns . /// /// - /// HTMX 1.x and 2.x trigger these events when the response is received, + /// HTMX 1.9.x and 2.x trigger these events when the response is received, /// whereas HTMX 4.x triggers them when the request completes /// (after the swap and settle steps whenever a swap is performed). /// In HTMX 4.x, the list also includes the events added with other timings, diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseHeadersTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseHeadersTests.cs index 95c4779..d0f89a1 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseHeadersTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseHeadersTests.cs @@ -91,7 +91,7 @@ public void SettingNull_DoesNotAddHeader() var context = TestHelper.CreateHttpContext(); var headers = context.Response.GetHtmxHeaders(); - headers.Location = null!; + headers.Location = null; Assert.That(context.Response.Headers.ContainsKey(HtmxResponseHeaderNames.Location), Is.False); }