Skip to content

Commit 5a6be96

Browse files
committed
fix: throw invalid operation exception if attempting to set hx-response headers on a non-hx request
1 parent 4f19d7e commit 5a6be96

File tree

2 files changed

+32
-3
lines changed

2 files changed

+32
-3
lines changed

src/Htmxor/Http/HtmxResponse.cs

+31-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public sealed class HtmxResponse(HttpContext context)
1212
{
1313
private const string ItemsKeyPrefix = "02E0A668-6E6B-4C53-83A6-17E576073E96";
1414
private readonly IHeaderDictionary headers = context.Response.Headers;
15+
private readonly bool isHtmxRequest = context.Request.Headers.ContainsKey(HtmxRequestHeaderNames.HtmxRequest);
1516

1617
internal bool EmptyResponseBodyRequested { get; private set; }
1718

@@ -21,6 +22,7 @@ public sealed class HtmxResponse(HttpContext context)
2122
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
2223
public HtmxResponse StatusCode(HttpStatusCode statusCode)
2324
{
25+
AssertIsHtmxRequest();
2426
context.Response.StatusCode = (int)statusCode;
2527
return this;
2628
}
@@ -32,6 +34,7 @@ public HtmxResponse StatusCode(HttpStatusCode statusCode)
3234
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
3335
public HtmxResponse EmptyBody()
3436
{
37+
AssertIsHtmxRequest();
3538
EmptyResponseBodyRequested = true;
3639
return this;
3740
}
@@ -43,6 +46,7 @@ public HtmxResponse EmptyBody()
4346
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
4447
public HtmxResponse Location(string path)
4548
{
49+
AssertIsHtmxRequest();
4650
headers[HtmxResponseHeaderNames.Location] = path;
4751
return this;
4852
}
@@ -54,6 +58,7 @@ public HtmxResponse Location(string path)
5458
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
5559
public HtmxResponse Location(LocationTarget locationTarget)
5660
{
61+
AssertIsHtmxRequest();
5762
var json = JsonSerializer.Serialize(locationTarget, HtmxJsonSerializerContext.Default.LocationTarget);
5863
headers[HtmxResponseHeaderNames.Location] = json;
5964
return this;
@@ -66,6 +71,7 @@ public HtmxResponse Location(LocationTarget locationTarget)
6671
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
6772
public HtmxResponse PushUrl(string url)
6873
{
74+
AssertIsHtmxRequest();
6975
headers[HtmxResponseHeaderNames.PushUrl] = url;
7076
return this;
7177
}
@@ -77,8 +83,8 @@ public HtmxResponse PushUrl(string url)
7783
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
7884
public HtmxResponse PreventBrowserHistoryUpdate()
7985
{
86+
AssertIsHtmxRequest();
8087
headers[HtmxResponseHeaderNames.PushUrl] = "false";
81-
8288
return this;
8389
}
8490

@@ -89,8 +95,8 @@ public HtmxResponse PreventBrowserHistoryUpdate()
8995
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
9096
public HtmxResponse PreventBrowserCurrentUrlUpdate()
9197
{
98+
AssertIsHtmxRequest();
9299
headers[HtmxResponseHeaderNames.ReplaceUrl] = "false";
93-
94100
return this;
95101
}
96102

@@ -101,6 +107,7 @@ public HtmxResponse PreventBrowserCurrentUrlUpdate()
101107
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
102108
public HtmxResponse Redirect(string url)
103109
{
110+
AssertIsHtmxRequest();
104111
headers[HtmxResponseHeaderNames.Redirect] = url;
105112
EmptyResponseBodyRequested = true;
106113
return this;
@@ -112,6 +119,7 @@ public HtmxResponse Redirect(string url)
112119
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
113120
public HtmxResponse Refresh()
114121
{
122+
AssertIsHtmxRequest();
115123
headers[HtmxResponseHeaderNames.Refresh] = "true";
116124
EmptyResponseBodyRequested = true;
117125
return this;
@@ -124,6 +132,7 @@ public HtmxResponse Refresh()
124132
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
125133
public HtmxResponse ReplaceUrl(string url)
126134
{
135+
AssertIsHtmxRequest();
127136
headers[HtmxResponseHeaderNames.ReplaceUrl] = url;
128137
return this;
129138
}
@@ -135,11 +144,12 @@ public HtmxResponse ReplaceUrl(string url)
135144
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
136145
public HtmxResponse Reswap(SwapStyle swapStyle)
137146
{
147+
AssertIsHtmxRequest();
148+
138149
var style = swapStyle switch
139150
{
140151
SwapStyle.InnerHTML => "innerHTML",
141152
SwapStyle.OuterHTML => "outerHTML",
142-
//SwapStyle.TextContent => "textContent",
143153
SwapStyle.BeforeBegin => "beforebegin",
144154
SwapStyle.AfterBegin => "afterbegin",
145155
SwapStyle.BeforeEnd => "beforeend",
@@ -161,6 +171,8 @@ public HtmxResponse Reswap(SwapStyle swapStyle)
161171
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
162172
public HtmxResponse Retarget(string selector)
163173
{
174+
AssertIsHtmxRequest();
175+
164176
headers[HtmxResponseHeaderNames.Retarget] = selector;
165177

166178
return this;
@@ -173,6 +185,8 @@ public HtmxResponse Retarget(string selector)
173185
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
174186
public HtmxResponse Reselect(string selector)
175187
{
188+
AssertIsHtmxRequest();
189+
176190
headers[HtmxResponseHeaderNames.Reselect] = selector;
177191

178192
return this;
@@ -186,6 +200,8 @@ public HtmxResponse Reselect(string selector)
186200
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
187201
public HtmxResponse Trigger(string eventName, TriggerTiming timing = TriggerTiming.Default)
188202
{
203+
AssertIsHtmxRequest();
204+
189205
var headerKey = timing switch
190206
{
191207
TriggerTiming.AfterSwap => HtmxResponseHeaderNames.TriggerAfterSwap,
@@ -209,6 +225,8 @@ public HtmxResponse Trigger(string eventName, TriggerTiming timing = TriggerTimi
209225
/// <returns>This <see cref="HtmxResponse"/> object instance.</returns>
210226
public HtmxResponse Trigger<TEventDetail>(string eventName, TEventDetail detail, TriggerTiming timing = TriggerTiming.Default, JsonSerializerOptions? jsonSerializerOptions = null)
211227
{
228+
AssertIsHtmxRequest();
229+
212230
var headerKey = timing switch
213231
{
214232
TriggerTiming.AfterSwap => HtmxResponseHeaderNames.TriggerAfterSwap,
@@ -254,4 +272,14 @@ public override string ToString()
254272
? $"\"{EventName}\":null"
255273
: $"\"{EventName}\":{Detail}";
256274
}
275+
276+
private void AssertIsHtmxRequest()
277+
{
278+
if(!isHtmxRequest)
279+
{
280+
throw new InvalidOperationException(
281+
"The active request is not an htmx request. " +
282+
"Setting response headers during request has no effect.");
283+
}
284+
}
257285
}

test/Htmxor.Tests/Http/HtmxResponseTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ private static HttpContext CreateHttpContext()
1212
{
1313
RequestServices = new ServiceCollection().BuildServiceProvider()
1414
};
15+
result.Request.Headers[HtmxRequestHeaderNames.HtmxRequest] = "";
1516
result.GetHtmxContext();
1617
return result;
1718
}

0 commit comments

Comments
 (0)