Page 29 - MSDN Magazine, September 2019
P. 29
is specified by an inner choose function and its list of routes. As you can see, within that list of routes, there’s a route that specifies the “/hello” string as its route pattern.
This route is actually another F# function, called route. It takes a string parameter to specify the route pattern, and is then composed with the >=> operator. This is a way of saying, “this route corre- sponds to the function that follows it.” This kind of composition is what’s known as Kleisli composition, and although it’s not essential to understand that theoretical underpinning to use the operator, it’s worth knowing that it has a firm mathematical foundation. As I mentioned earlier in the article, F# developers lean toward cor- rectness ... and what’s more correct than a mathematical basis?
You’ll notice that the function on the right-hand side of the >=> operator, handleGetHello, is defined elsewhere. Let’s open up the file it’s defined in, HttpHandlers.fs:
let handleGetHello =
fun (next: HttpFunc) (ctx: HttpContext) ->
task {
let response = {
Text = "Hello world, from Giraffe!" }
return! json response next ctx }
Unlike “normal” F# functions, this handler function is actually defined as a lambda: It’s a first-class function. Although this style isn’t quite so common in F#, it’s chosen because the two parameters that the lambda takes—next and ctx—are typically constructed and passed to the handler by the underlying ASP.NET Core runtime, not necessarily user code. From our perspective as programmers, we don’t need to pass these around ourselves.
These parameters defined in the lambda function are abstrac- tions that are defined in and used by the ASP.NET Core runtime itself. Once in the body of the lambda function, with these param- eters, you can construct any object you’d like to serialize and send down the ASP.NET Core pipeline. The value called response is an instance of an F# record type that contains a single label, Text. Because Text is a string, it’s given a string to serialize. The defini- tion of this type resides in the Models.fs file. The function then returns a JSON-encoded representation of the response, with the next and ctx parameters.
Another way you can look at a Giraffe pipeline is with some small boilerplate at the top and bottom of a function, to conform totheASP.NETCorepipelineabstractionandthenanythingyou want in between:
let handlerName =
fun (next: HttpFunc) (ctx: HttpContext) ->
task {
// Do anything you want here //
// ... Well, anything within reason! //
// Eventually, you’ll construct a response value of some kind,
// and you’ll want to serialize it (as JSON, XML or whatever). //
// Giraffe has multiple middleware-function utilities you can call.
return! middleware-function response next ctx
Although this may seem like a lot to learn up front, it’s quite pro- ductiveandeasytobuildWebappsandserviceswith.Todemonstrate this, I’ll add a new handler function in the HttpHandlers.fs file that gives a greeting if you specify your name:
msdnmagazine.com
let handleGetHelloWithName (name: string) = fun (next: HttpFunc) (ctx: HttpContext) ->
task {
let response = {
}
Text = sprintf "Hello, %s" name }
return! json response next ctx
As before, I set up this handler with the necessary boilerplate to con- form to ASP.NET Core middleware. A key difference is that my handler function takes a string as input. I use the same response type as before.
Next, I’ll add a new route in the Program.fs file, but because I want to specify some arbitrary string as input, I’ll need to use something other than the route function. Giraffe defines the routef function exactly for this purpose:
let webApp = choose [
subRoute "/api" (choose [
GET >=> choose [
route "/hello" >=> handleGetHello
// New route function added here
routef "/hello/%s" handleGetHelloWithName ]
])
setStatusCode 404 >=> text "Not Found" ]
The routef function takes in two inputs:
• A format string that represents the route and its input
(in this case, a string with %s)
• A handler function (that I defined earlier)
You’ll note that I didn’t supply the >=> operator here. This is because routef has two parameters: the string pattern (specified by an F# format string) and the handler that operates on types specified by the F# format string. This is in contrast with the route function, which only takes a string pattern as input. In this case, because I don’t need to compose routef and my handler with anything else, I don’t use the >=> operator to compose additional handlers. But if I wanted to do something like set a specific HTTP status code, I’d do that by composing with >=>.
Lots of F# developers use Giraffe because of how flexible it is, and also because it has such a rock-solid foundation as it uses ASP.NET Core under the covers.
Now I can rebuild the app and navigate to https://localhost:5001/ api/hello/phillip. When I do, I get:
{"text":"Hello, Phillip”}
Ta-da! Pretty easy, right? As with any library or framework, there are a few things to learn, but once you’re comfortable with the abstractions it’s incredibly easy to add routes and handlers that do what you need.
You can read more about how Giraffe works in its excellent documentation (bit.ly/2GqBVhT). And you’ll find a runnable sample app that shows what I demonstrated at bit.ly/2Z21yNq.
}
September 2019 21