A toolkit for building fast and functional-first web applications using F#.
webHost
Builder ImprovementsIn previous versions, the minimalistic web host builder allowed devs to get running quickly and when needed enabled full customization via the configure
custom operation. This meant that anything beyond a toy project, required a fair amount of repetitive boilerplate setup code.
To combat this, several custom operations have been added, which semantically match the relevant area (i.e., add_service
for services, use_middleware
for middleware). The goal is to avoid the need to engage in a full-fledged configuration, although the configure
method still exists which will override all other customizations which also creates a kind backward compatibility.
In addition, many common operations have been explicitly mapped: use_static_files
, use_https
, use_compression
etc.
An example of the new builder in action can be found in the docs or samples.
configuration
Builder AddedA thin wrapper around ConfigurationBuilder
exposing a clean API for reading configuration values.
open Falco.HostBuilder
[<EntryPoint>]
let main args =
let env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
let config = configuration args {
add_env
required_json "appsettings.json"
optional_json (String.Concat([|"appsettings."; env; ".json"|]))
}
StringUtils.stringf
StringUtils.strSplit
CookieCollectionReader
Auth.getClaimValue
Auth.hasScope
Request
functionalityRequest.getCookie
Request.tryBindCookie
Request.streamForm
Request.tryBindFormStream
Request
HttpHandler'sRequest.mapCookie
Request.bindCookie
Request.ifAuthenticatedWithScope
Request
HttpHandler's for streaming multipart dataTwo particular fundamental handlers have been added to the Request module, to support multipart form data streaming for large uploads which Microsoft defines large uploads as anything > 64KB.
Request.bindFormStream
Request.bindFormStreamSecure
Request.mapFormStream
Request.mapFormStreamSecure
Response
HttpHandler'sHandlers have been added to support binary responses, both inline and attachment. Both asynchronously buffer data into the response body.
Response.ofBinary
Response.ofAttachment
With .NET 5.0 finally here, it seemed like a good time to move to v3.x.x which will support both the netcoreapp3.1
and net5.0
build targets. The major version upgraded represented an opportunity to re-evaulate certain features of the API and determine if there were any missed opportunties.
The most practical upgrade was surrounding IHost
creating, for which a computation expression has been included: webHost args { ... }
. With that came registration & activation extension methods for IServiceCollection
and IApplicationBuilder
respectively. They are aptly named services.AddFalco()
and app.UseFalco(endpoints)
. The global exception handler hook has been renamed app.UseFalcoExceptionHandler(...)
.
How you interact with header and route values, now directly matches interactions with queries and forms, all enabled by the StringCollectionReader
. A third set of methods was added to this class supporting "get or default" functionality.
Please note, that the
?
dynamic operator has been removed.
Listed below is the full list of additions and removals:
IServiceCollection.AddFalco
IServiceCollection.AddFalco (routeOptions : RouteOptions -> unit)
IApplicationBuilder.UseFalco (endpoints : HttpEndpoint list)
IApplicationBuilder.UseFalcoExceptionHandler (exceptionHandler : HttpHandler)
QueryCollectionReader
replacing direct usage of StringCollectionReader
HeaderCollectionReader
RouteCollectionReader
HttpRequest.GetHeader
HttpRequest.GetRouteValues
HttpRequest.GetRouteReader
type ExceptionHandler
type ExceptionHandlingMiddleware
Host.defaultExceptionHandler
Host.defaultNotFoundHandler
Host.startWebHostDefault
Host.startWebHost
IApplicationBuilder.UseHttpEndpoints (endpoints : HttpEndpoint list)
IApplicationBuilder.UseFalco (endpoints : HttpEndpoint list)
Request.getHeader
Request.getRouteValues
Request.getRoute
Request.tryGetRouteValue
?
dynamic operatorRun
member to ensure that automatic Task<unit> -> Task
still occurs.Html.h1
vs h1
Attr.class'
vs _class
HttpHandler
function which accept another HttpHandler
as a parameter.startWebHost
and startWebHostDefault
) simplify IHost
creation.Response.withStatusCode
vs ctx.SetStatusCode
).
HttpContext
based modules exist for: Request, Response, Auth & XssThis is a general guide on migrating v1.x.x code to v2.0.0. Both sample apps have been updated and serve as more complete references.
HttpHandler
(HttpContext -> Task
) now resembles that of a native RequestDelegate
HttpResponse
, called HttpResponseModifier
with a definition of HttpContext -> HttpContext
HttpRequest
or HttpResponse
is now achieved through the Request
and Response
modules respectively.An example:
// v1.x.x
let notFound : HttpHandler =
setStatusCode 404
>=> textOut "Not Found"
// v2.0.0
let notFound : HttpHandler =
Response.withStatusCode 404
>> Response.ofPlainText "Not found"
Another example:
// v.1.x.x
let helloHandler : HttpHandler =
fun next ctx ->
let greeting =
ctx.tryGetRouteValue "name"
|> Option.defaultValue "someone"
|> sprintf "hi %s"
textOut
// v2.0.0
let helloHandler : HttpHandler =
fun ctx ->
let greeting =
Request.tryGetRouteValue "name" ctx
|> Option.defaultValue "someone"
|> sprintf "hi %s"
Response.ofPlainText greeting ctx
Another example:
// v1.x.x
let exampleTryBindFormHandler : HttpHandler =
tryBindForm
(fun r ->
Ok {
FirstName = form?FirstName.AsString()
LastName = form?LastName.AsString()
Age = form?Age.AsInt16()
})
errorHandler
successHandler
// v2.0.0
let exampleTryBindFormHandler : HttpHandler =
fun ctx ->
let bindForm form =
{
FirstName = form?FirstName.AsString()
LastName = form?LastName.AsString()
Age = form?Age.AsInt16()
}
let respondWith =
match Request.tryBindForm (bindForm >> Result.Ok) ctx with
| Error error -> Response.ofPlainText error
| Ok model -> Response.ofPlainText (sprintf "%A" model)
respondWith ctx
Falco.ViewEngine
becomes Falco.Markup
Elem
. Thus div
becomes Elem.div
Falco.Markup.Elem
and use without Elem.
prefixAttr
. This _class
becomes Attr.class'
class
Text
. Thus raw
becomes Text.raw
Falco.Markup.Text
and use without Text.
prefixAn example:
// v1.x.x
let doc =
html [] [
head [] [
title [] [ raw "Sample App" ]
]
body [] [
h1 [] [ raw "Sample App" ]
]
]
// v2.0.0
let doc =
Elem.html [ Attr.lang "en" ] [
Elem.head [] [
Elem.title [] [ Text.raw "Sample App" ]
]
Elem.body [] [
Elem.main [] [
Elem.h1 [] [ Text.raw "Sample App" ]
]
]
]
Enhancements from v1.x.x:
Html.h1
vs h1
Attr.class'
vs _class
HttpHandler
function which accept another HttpHandler
as a parameter.startWebHost
and startWebHostDefault
) simplify IHost
creation.Response.withStatusCode
vs ctx.SetStatusCode
).
HttpContext
based modules exist for: Request, Response, Auth & Xsstask -> async -> task
conversions
Enhancements:
HttpContext
extension method to obtain current user as ClaimsPrincipal option
HttpContext
extension method to generate and return AntiforgeryTokenSet
authCsrfHtmlOut
, which threads both ClaimsPrincipal
and AntiforgeryTokenSet
through a provided custom HttpHandler
UseExceptionMiddlewate
extension method on IApplicationBuilder
to enable Falco exception handlingRemovals:
webApp { ... }
computation expression (see issue #21 for discussion, and #22 for details)