Programming adventures

Last week I announced the release of Giraffe 1.0.0, which (apart from some initial confusion around the transition to TaskBuilder.fs) went mostly smoothly. However, if you have thought that I would be chilling out much since then, then you'll probably be disappointed to hear that today I've released another version of Giraffe with more exciting features and minor bug fixes.

However, it is not uncommon that a web application chooses to not distinguish between two routes with and without a trailing slash and as such it wasn't a surprise when I received multiple bug reports for Giraffe not doing this by default.

Before version 1.1.0 one would have had to specify two individual routes in order to make it work:

The problem with above code is that the routing pipeline will always check if a user is authenticated (and potentially return an error response) before even knowing if all subsequent routes require it.

The workaround was to move the authentication check into each of the individual handlers, namely the userFooHandler and the userBarHandler in this instance.

A more elegant way would have been to specify the authentication handler only one time before declaring all protected routes in a single group. Normally the subRoute http handler would make this possible, but not if routes have parameterised arguments at the beginning of their paths.

In this example we have a typical F# record type called Adult. The Adult type has an override for its ToString() method to output something more meaningful than .NET's default and an additional member called HasErrors() which checks if the provided data is correct according to the application's business rules (e.g. an adult must have an age of 18 or over).

There's a few problems with this implementation though. First you must know that the BindQueryString<'T> extension method is a very loose model binding function, which means it will create an instance of type Adult even if some of the mandatory fields (non optional parameters) were not present in the query string (or badly formatted). While this "optimistic" model binding approach has its own advantages, it is not very idiomatic to functional programming and requires additional null checks in subsequent code.

Secondly the model validation has been baked into the personHandler which is not a big problem at first, but means that there's a lot of boilerplate code to be written if an application has more than just one model to work with.

Giraffe 1.1.0 introduces new http handler functions which make model binding more functional. The new tryBindQuery<'T> http handler is a stricter model binding function, which will only create an instance of type 'T if all mandatory fields have been provided by the request's query string. It will also make sure that the provided data is in the correct format (e.g. a numeric value has been provided for an int property of the model) before returning an object of type 'T:

The tryBindQuery<'T> requires three parameters. The first is an error handling function of type string -> HttpHandler which will get invoked when the model binding fails. The string parameter in that function will hold the specific model parsing error message. The second parameter is an optional CultureInfo object, which will get used to parse culture specific data such as DateTime values or floating point numbers. The last parameter is a function of type 'T -> HttpHandler, which will get invoked with the parsed model if model parsing was successful.

By using tryBindQuery<'T> there is no danger of encountering a NullReferenceException or the need of doing additional null check any more. By the time the model has been passed into the adultHandler it has been already validated against any data contract violations (e.g. all mandatory fields have been provided, etc.).

At this point the semantic validation of business rules is still embedded in the adultHandler itself. The IModelValidation<'T> interface can help to move this validation step closer to the model and make use of a more generic model validation function when composing the entire web application together:

By implementing the IModelValidation<'T> interface on the Adult record type we can now make use of the validateModel http handler when composing the /person route. This functional composition allows us to entirely get rid of the adultHandler and keep a clear separation of concerns.

First the tryBindQuery<Adult> handler will parse the request's query string and create an instance of type Adult. If the query string had badly formatted or missing data then the parsingErrorHandler will be executed, which allows a user to specify a custom error response for data contract violations. If the model could be successfully parsed, then the validateModel http handler will be invoked which will now validate the business rules of the model (by invoking the IModelValidation.Validate() method). The user can specify a different error response for business rule violations when implementing the IModelValidation<'T> interface. Lastly if the model validation succeeded then the textHandler will be executed which will simply use the object's ToString() method to return a HTTP 200 text response.

All functions are generic now so that adding more routes for other models is just a matter of implementing a new record types for each model and registering a single route in the web application's composition:

Overall the new model binding and model validation API aims at providing a more functional counter part to MVC's model validation, except that Giraffe prefers to use functions and interfaces instead of the System.ComponentModel.DataAnnotations attributes. The benefit is that data attributes are often ignored by the rest of the code while a simple validation function can be used from outside Giraffe as well. F# also has the benefit of having a better type system than C#, which means that things like the [<Required>] attribute have little use if there is already an Option<'T> type.

Currently this new improved way of model binding in Giraffe only works for query strings and HTTP form payloads via the tryBindQuery<'T> and tryBindFrom<'T> http handler functions. Model binding functions for JSON and XML remain with the "optimistic" parsing model due to the underlying model binding libraries (JSON.NET and XmlSerializer), but a future update with improvements for JSON and XML is planned as well.

In total you have the following new model binding http handlers at your disposal with Giraffe 1.1.0:

HttpHandler

Description

bindJson

Traditional model binding. This is a new http handler equivalent of ctx.BindJsonAsync.

bindXml

Traditional model binding. This is a new http handler equivalent of ctx.BindAsync.

bindForm

Traditional model binding. This is a new http handler equivalent of ctx.BindFormAsync.

tryBindForm

New improved model binding. This is a new http handler equivalent of a new HttpContext extension method called ctx.TryBindFormAsync.

bindQuery

Traditional model binding. This is a new http handler equivalent of ctx.BindQueryString.

tryBindQuery

New improved model binding. This is a new http handler equivalent of a new HttpContext extension method called ctx.TryBindQueryString.

bindModel

Traditional model binding. This is a new http handler equivalent of ctx.BindModelAsync.

The new model validation API works with any http handler which returns an object of type 'T and is not limited to tryBindQuery<'T> and tryBindFrom<'T> only.

Roadmap overview

To round up this blog post I thought I'll quickly give you a brief overview of what I am planning to tackle next.

The next release of Giraffe is anticipated to be version 1.2.0 (no date set yet) which will mainly focus around improved authentication and authorization handlers (policy based auth support), better CORS support and hopefully better Anti-CSRF support.

I am pleased to announce the release of Giraffe 1.0.0, a functional ASP.NET Core web framework for F# developers. After more than a year of building, improving and testing the foundations of Giraffe it makes me extremely happy to hit this important milestone today. With the help of 32 independent contributors, more than a hundred closed GitHub issues and an astonishing 79 merged pull requests (and counting) it is fair to say that Giraffe has gone through many small and big changes which made it what I believe one of the best functional web frameworks available today.

The release of Giraffe 1.0.0 continues with this trend and also brings some new features and improvements along the way:

Streaming support

Giraffe 1.0.0 offers a new streaming API which can be used to stream (large) files and other content directly to a client.

A lot of work has been put into making this feature properly work like supporting conditional HTTP headers and range processing capabilities. On top of that I was even able to help iron out a few bugs in ASP.NET Core MVC's implementation as well (loving the fact that ASP.NET Core is all open source).

Conditional HTTP Headers

In addition to the new streaming API the validation of conditional HTTP headers has been exposed as a separate feature too. The ValidatePreconditions function is available as a HttpContext extension method which can be used to validate If-{...} HTTP headers from within any http handler in Giraffe. The function will self determine the context in which it is called (e.g. GETPOST, PUT, etc.) and return a correct result denoting whether a request should be further processed or not.

Configuration of serializers

A much desired and important improvement was the ability to change the default implementation of data serializers and content negotiation. Giraffe 1.0.0 allows an application to configure the default JSON or XML serializer via ASP.NET Core's services container.

Detailed XML documentation

For the first time Giraffe has detailed XML documentation for all public facing functions available:

Even though this is not a feature itself, it aims at improving the general development experience by providing better IntelliSense and more detailed information when working with Giraffe.

Giraffe.Tasks deprecated

When Giraffe introduced the task {} CE for the first time it was a copy of the single file project TaskBuilder.fs written by Robert Peele. However, maintaining our own copy of the task CE is resource expensive and not exactly my personal field of expertise. Besides that, since the initial release Robert has made great improvements to TaskBuilder.fs whereas Giraffe's version has been lacking behind. When TaksBuilder.fs has been published to NuGet it felt like a good idea to deprecate Giraffe.Tasks and resort back to the original.

This allows me and other Giraffe contributors to focus more on the web part of Giraffe and let Robert do his excellent work on the async/task side of things. Otherwise nothing has changed and Giraffe will continue to build on top of Task and Task<'T>. If you use Giraffe.Tasks outside of a Giraffe web application then you can continue doing so by referencing TaskBuilder.fs instead.

Giraffe also continues to use exclusively the context insensitive version of the task CE (meaning all task objects are awaited with ConfigureAwait(false)). If you encouter type inference issues after the upgrade to Giraffe 1.0.0 then you might have to add an extra open statement to your F# file:

open FSharp.Control.Tasks.ContextInsensitive

This is normally not required though unless you have do! bindings in your code.

If you like the task {} computation expression then please go to the official GitHub repository and hit the star button to show some support!

TokenRouter as NuGet package

TokenRouter is a popular alternative to Giraffe's default routing API aimed at providing maximum performance. Given the complexity of TokenRouter and the fact that Giraffe already ships a default version of the routing API it made only sense to decouple the TokenRouter into its own repository.

This change will allow TokenRouter to become more independent and evolve at its own pace. TokenRouter can also benefit from having its own release cycle and be much bolder in introducing new features and breaking changes without affecting Giraffe.

If your project is using the TokenRouter API then you will need to add a new dependency to the Giraffe.TokenRouter NuGet package now. The rest remains unchanged.

Improved documentation

At last I have worked on improving the official Giraffe documentation by completely restructuring the document, providing a wealth of new information and focusing on popular topics by demand.

The documentation has also been broken out of the README, but remains as a Markdown file in the git repository for reasons which I hope to blog about in a separate blog post soon.