Releases: falcoframework/Falco
v3.1
webHost Builder Improvements
In 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 Added
A 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"|]))
}Utility Additions
StringUtils.stringfStringUtils.strSplitCookieCollectionReaderAuth.getClaimValueAuth.hasScope
New Request functionality
Request.getCookieRequest.tryBindCookieRequest.streamFormRequest.tryBindFormStream
New Request HttpHandler's
Request.mapCookieRequest.bindCookieRequest.ifAuthenticatedWithScope
New Request HttpHandler's for streaming multipart data
Two 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.bindFormStreamRequest.bindFormStreamSecureRequest.mapFormStreamRequest.mapFormStreamSecure
New Response HttpHandler's
Handlers have been added to support binary responses, both inline and attachment. Both asynchronously buffer data into the response body.
Response.ofBinaryResponse.ofAttachment
v3.0
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:
Additions
IServiceCollection.AddFalcoIServiceCollection.AddFalco (routeOptions : RouteOptions -> unit)IApplicationBuilder.UseFalco (endpoints : HttpEndpoint list)IApplicationBuilder.UseFalcoExceptionHandler (exceptionHandler : HttpHandler)QueryCollectionReaderreplacing direct usage ofStringCollectionReaderHeaderCollectionReaderRouteCollectionReader
Removals
Extensions
HttpRequest.GetHeaderHttpRequest.GetRouteValuesHttpRequest.GetRouteReader
Exceptions
type ExceptionHandlertype ExceptionHandlingMiddleware
Host module
Host.defaultExceptionHandlerHost.defaultNotFoundHandlerHost.startWebHostDefaultHost.startWebHostIApplicationBuilder.UseHttpEndpoints (endpoints : HttpEndpoint list)- replaced by
IApplicationBuilder.UseFalco (endpoints : HttpEndpoint list)
- replaced by
Request module
Request.getHeaderRequest.getRouteValues- replace by
Request.getRoute
- replace by
Request.tryGetRouteValue
StringCollectionReader
?dynamic operator
v2.0
Release Notes
- The markup DSL is qualified instead of bare functions.
Html.h1vsh1Attr.class'vs_class
- Handlers considered end-to-end processors of a request.
- Continuations are still possible by creating new
HttpHandlerfunction which accept anotherHttpHandleras a parameter. - As a result of this change, performance has increased.
- Continuations are still possible by creating new
- Host setup functions have been added.
- These functions (
startWebHostandstartWebHostDefault) simplifyIHostcreation.
- These functions (
- Modular interop with the HttpContext.
- Optional online extension methods are used to enrich the base library. This functionality is now exposed in a modular fashion (i.e.
Response.withStatusCodevsctx.SetStatusCode).HttpContextbased modules exist for: Request, Response, Auth & Xss
- Optional online extension methods are used to enrich the base library. This functionality is now exposed in a modular fashion (i.e.
Migration Guide
This 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.
- The definition of an
HttpHandler(HttpContext -> Task) now resembles that of a nativeRequestDelegate - A new definition for any non-IO based modifications to the
HttpResponse, calledHttpResponseModifierwith a definition ofHttpContext -> HttpContext - Dealing with either the
HttpRequestorHttpResponseis now achieved through theRequestandResponsemodules 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 ctxAnother 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 ctxMarkup
Falco.ViewEnginebecomesFalco.Markup- Element level items now reside in a module called
Elem. ThusdivbecomesElem.div- ** You can import
Falco.Markup.Elemand use withoutElem.prefix
- ** You can import
- Element attributes are now reside in a module calls
Attr. This_classbecomesAttr.class'- ** Take note of the trailing apostrophe, which is used to delimit reserved keywords like
class
- ** Take note of the trailing apostrophe, which is used to delimit reserved keywords like
- Text elements now reside in a module called
Text. ThusrawbecomesText.raw- ** You can import
Falco.Markup.Textand use withoutText.prefix
- ** You can import
An 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" ]
]
]
]