5
comments
on 2/13/2015 3:43 AM

WebSharper Sitelets are a wonderful way to build websites quickly and safely. They can automatically manage your URLs in a type-safe way, and generate HTML markup using simple F# combinators. In WebSharper 3.0, we are extending Sitelets with the capability to create REST APIs with unrivaled simplicity.

Requests

Just like you can currently type these lines in F#:

1
2
3
type Action =
	| [<CompiledName "listing">] Listing of pagenum: int
    | [<CompiledName "article">] Article of id: int * slug: string

to tell WebSharper that your site will be served on these URLs:

1
2
/listing/1
/article/135/upcoming-in-websharper-30

You can now also specify all the information necessary to serve a web API on a given HTTP method and with the given JSON body, simply by using a couple attributes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Action =
	| [<Method "GET"; CompiledName "article">]
    	GetArticle of id: int
    | [<Method "POST"; CompiledName "article"; Json "data">]
    	PostArticle of data: ArticleData

and ArticleData =
	{
    	author: string
        title: string
        tags: Set<string>
        summary: option<string>
        body: string
    }

The following requests are now accepted:

1
GET /article/135

1
2
3
4
5
6
7
8
9
POST /article

{
  "author": "loic.denuziere",
  "title": "Upcoming in WebSharper 3.0: serving REST APIs, easy as pie!",
  "tags": ["websharper", "fsharp"],
  "summary": "WebSharper 3.0 is coming with an (...)",
  "body": "WebSharper Sitelets are a wonderful way to (...)"
}

The JSON serialization provides all the niceties possible to be friendly with F# types:

  • F# records are represented as JSON objects;
  • list<'T>, 'T[] and Set<'T> are representad as JSON arrays;
  • Map<string, 'T> is represented as a flat JSON object;
  • F# fields of type option<'T> are represented as a JSON field that is present if Some or absent if None;
  • F# unions are represented as JSON objects using the union field names, and a separate named field to indicate the union case (the name of this field is specified in an attribute).

Responses

Of course, a REST API is not just parsing requests, but also writing responses. For this too, WebSharper 3.0 has you covered. A new function Content.JsonContent allows you to serve any F# value as JSON with zero hassle:

1
2
3
4
5
6
7
let mySite = Sitelet.Infer <| function
	| GetArticle id ->
    	Content.JsonContent <| fun ctx ->
        	{ author = "loic.denuziere"; (* ... *) }
    | PostArticle articleData ->
    	Content.JsonContent <| fun ctx ->
        	SaveArticle articleData

Want to see a full example? How about a full CRUD API serving an in-memory database of people information, with all interactions perfectly type-safe, in less than fifty lines?

Look for this new WebSharper 3.0 pre-release on NuGet early next week, or build it right now from source!

.

Hi! This looks really interesting feature for WebSharper. I'm quite newbie with WebApi and WebSharper, so one question: any idea how to allow CORS with WebSharper REST API?

By on 3/3/2015 8:14 AM ()

CORS support involves two steps: the preflight OPTIONS request (if you use other methods than GET and POST), and the main request.

Here is an example to handle both parts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// HELPER FUNCTIONS

// Adds the Access-Control-Allow-Origin header if the request has an Origin header.
let AddAllowOrigin (content: Content<'T>) : Content<'T> =
    Content.CustomContentAsync <| fun ctx -> async {
        let! response = Content.ToResponseAsync content ctx
        let headers =
            ctx.Request.Headers
            |> Seq.tryFind (fun h -> h.Name = "Origin")
            |> Option.map (fun origin ->
                Http.Header.Custom "Access-Control-Allow-Origin" origin.Value)
            |> Option.toList
        return { response with Headers = Seq.append headers response.Headers }
    }

// Creates a preflight response that allows the given methods.
let Preflight (methods: seq<string>) : Content<'T> =
    Content.CustomContent <| fun ctx ->
        let methods = String.concat ", " methods
        let headers = [ Http.Header.Custom "Access-Control-Allow-Methods" methods ]
        { Status = Http.Status.Ok; Headers = headers; WriteBody = ignore }
    |> AddAllowOrigin


// APPLICATION CODE

type MyAction =
    // preflight request
    | [<Method "OPTIONS"; CompiledName "my-api">] MyApiOptions
    // main request
    | [<Method "PUT"    ; CompiledName "my-api">] MyApiPut of SomeArgs

let mySite = Sitelet.Infer <| function
    // Respond to the preflight request
    | MyApiOptions -> Preflight ["PUT"]
    // Respond to the actual request
    | MyApiPut args ->
        Content.JsonContent <| fun ctx ->
            () // Generate your body here...
        |> AddAllowOrigin

I hope this helps.

By on 3/3/2015 11:11 AM ()

Thanks a lot! Works like a charm

By on 3/5/2015 9:42 AM ()

Looks great! However, the link to the full example code does not seem to work.

By on 3/30/2015 1:47 AM ()

Indeed, thanks! I fixed the link.

By on 3/30/2015 1:58 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us
Built with WebSharper