Skip to content

Commit 252eacf

Browse files
committed
5.1.0, Response.ofFragment
1 parent 925c5d2 commit 252eacf

5 files changed

Lines changed: 55 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [5.1.0] - 2025-09-09
6+
7+
### Added
8+
9+
- `Response.ofFragment` and `Response.ofFragmentCsrf` to return HTML fragments by element ID, with optional CSRF token support.
10+
- `Falco.Markup` [version 1.4.0](https://www.nuget.org/packages/Falco.Markup/1.4.0), which reverted API to 1.2.0 and provided correct support for template fragments.
11+
512
## [5.0.3] - 2025-09-09
613

714
### Added
815

9-
- Updated Falco.Markup to 1.3.0, which enables support for HTML fragment responses.
16+
- `Falco.Markup` [version 1.3.0](https://www.nuget.org/packages/Falco.Markup/1.3.0), which enables support for template fragment responses.
1017

1118
## [5.0.2] - 2025-06-26
1219

src/Falco/Falco.fsproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<AssemblyName>Falco</AssemblyName>
4-
<Version>5.0.3</Version>
4+
<Version>5.1.0</Version>
55

66
<!-- General info -->
77
<Description>A functional-first toolkit for building brilliant ASP.NET Core applications using F#.</Description>
@@ -39,7 +39,7 @@
3939
</ItemGroup>
4040

4141
<ItemGroup>
42-
<PackageReference Include="Falco.Markup" Version="1.3.*" />
42+
<PackageReference Include="Falco.Markup" Version="1.4.*" />
4343
<PackageReference Update="FSharp.Core" Version="6.0.0" />
4444
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.*" PrivateAssets="All" />
4545
</ItemGroup>

src/Falco/Response.fs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,25 +161,34 @@ let ofHtmlString
161161
/// Returns a "text/html; charset=utf-8" response with provided HTML to client.
162162
let ofHtml
163163
(html : XmlNode) : HttpHandler =
164-
let render =
165-
match html with
166-
| NodeList _ -> renderNode
167-
| SelfClosingNode _
168-
| ParentNode _
169-
| TextNode _ -> renderHtml
164+
ofHtmlString (renderHtml html)
170165

171-
ofHtmlString (render html)
166+
let private withCsrfToken handleToken : HttpHandler = fun ctx ->
167+
let csrfToken = Xsrf.getToken ctx
168+
handleToken csrfToken ctx
172169

173170
/// Returns a CSRF token-dependant "text/html; charset=utf-8" response with
174171
/// provided HTML to client.
175172
let ofHtmlCsrf
176173
(view : AntiforgeryTokenSet -> XmlNode) : HttpHandler =
177-
let withCsrfToken handleToken : HttpHandler = fun ctx ->
178-
let csrfToken = Xsrf.getToken ctx
179-
handleToken csrfToken ctx
180-
181174
withCsrfToken (fun token -> token |> view |> ofHtml)
182175

176+
/// Returns a "text/html; charset=utf-8" response with provided HTML fragment,
177+
/// if found, to client. If no element with the provided id is found, an empty
178+
/// string is returned.
179+
let ofFragment
180+
(id : string)
181+
(html : XmlNode) : HttpHandler =
182+
ofHtmlString (renderFragment html id)
183+
184+
/// Returns a CSRF token-dependant "text/html; charset=utf-8" response with
185+
/// provided HTML fragment, if found, to client. If no element with the
186+
/// provided id is found, an empty string is returned.
187+
let ofFragmentCsrf
188+
(id : string)
189+
(view : AntiforgeryTokenSet -> XmlNode) : HttpHandler =
190+
withCsrfToken (fun token -> token |> view |> ofFragment id)
191+
183192
/// Returns an optioned "application/json; charset=utf-8" response with the
184193
/// serialized object provided to the client.
185194
let ofJsonOptions

src/Falco/Security.fs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@ module Xsrf =
1515
Attr.name token.FormFieldName
1616
Attr.value token.RequestToken ]
1717

18-
/// Generates a CSRF token using the Microsoft.AspNetCore.Antiforgery
19-
/// package.
18+
/// Generates an antiforgery token and stores it in the user's cookies.
2019
let getToken (ctx : HttpContext) : AntiforgeryTokenSet =
2120
let antiFrg = ctx.RequestServices.GetRequiredService<IAntiforgery>()
2221
antiFrg.GetAndStoreTokens ctx
2322

24-
/// Validates the Antiforgery token within the provided HttpContext.
23+
/// Validates the antiforgery token within the provided HttpContext.
2524
let validateToken (ctx : HttpContext) : Task<bool> =
2625
let antiFrg = ctx.RequestServices.GetRequiredService<IAntiforgery>()
2726
antiFrg.IsRequestValidAsync ctx

test/Falco.Tests/ResponseTests.fs

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -197,30 +197,6 @@ let ``Response.ofHtml produces text/html result`` () =
197197
contentType |> should equal "text/html; charset=utf-8"
198198
}
199199

200-
[<Fact>]
201-
let ``Response.ofHtml products text/html for fragment`` () =
202-
let ctx = getHttpContextWriteable false
203-
let expected = "<div>1</div><div>2</div><div>3</div>"
204-
205-
let fragment =
206-
Elem.createFragment [
207-
_div [] [ _text "1" ]
208-
_div [] [ _text "2" ]
209-
_div [] [ _text "3" ]
210-
]
211-
212-
task {
213-
do! ctx |> Response.ofHtml fragment
214-
let! body = getResponseBody ctx
215-
let contentLength = ctx.Response.ContentLength
216-
let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]
217-
218-
body |> should equal expected
219-
contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))
220-
contentType |> should equal "text/html; charset=utf-8"
221-
}
222-
223-
224200
[<Fact>]
225201
let ``Response.ofHtmlString produces text/html result`` () =
226202
let ctx = getHttpContextWriteable false
@@ -252,3 +228,26 @@ let ``Response.challengeAndRedirect`` () =
252228
ctx.Response.Headers.WWWAuthenticate.ToArray() |> should contain AuthScheme
253229
ctx.Response.Headers.Location.ToArray() |> should contain "/"
254230
}
231+
232+
[<Fact>]
233+
let ``Response.ofFragment returns the specified fragment`` () =
234+
let ctx = getHttpContextWriteable false
235+
let expected = """<div id="fragment1">1</div>"""
236+
237+
let html =
238+
Elem.div [] [
239+
_div [ _id_ "fragment1" ] [ _text "1" ]
240+
_div [ _id_ "fragment2" ] [ _text "2" ]
241+
]
242+
243+
task {
244+
do! ctx |> Response.ofFragment "fragment1" html
245+
246+
let! body = getResponseBody ctx
247+
let contentLength = ctx.Response.ContentLength
248+
let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]
249+
250+
body |> should equal expected
251+
contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))
252+
contentType |> should equal "text/html; charset=utf-8"
253+
}

0 commit comments

Comments
 (0)