self hosting WebApi

This is just a short howto as I think it might come handy, as I struggled a bit on some points.

I needed a simple way to self-host a REST-like service (returning XML data) with fine control on the structure of the results.
I had a look at Nancy and others but finally choose to use Microsofts WebApi as it’s fine for what I am trying to do.

preparing the solution

First thing to do is grab the nuget-package Microsoft.AspNet.WebApi.SelfHost.
In addition I needed references to System.Xml as I want to use XmlDocument.

helper class for XML content

I want the replies to have content type text/xml (instead of application/xml) and the only way I found is to use a helper class:

type XmlContent (document : System.Xml.XmlDocument) =
    inherit System.Net.Http.HttpContent ()

    let _stream = new System.IO.MemoryStream ()
    do
        document.Save _stream
        _stream.Position <- int64 0
        base.Headers.ContentType 
           <- new Http.Headers.MediaTypeWithQualityHeaderValue
                     ("text/xml")

    override this.SerializeToStreamAsync 
                     ( stream : System.IO.Stream
                     , context : System.Net.TransportContext) =
        async {
            _stream.CopyTo stream
            return ()
        } |> Async.StartAsTask :> System.Threading.Tasks.Task

    override this.TryComputeLength (length : byref<int64>) =
        length <- _stream.Length
        true

    override this.Dispose (disposing : bool) =
        base.Dispose(disposing)
        if disposing then
            _stream.Dispose()

As you can see you create an instance of this given a XmlDocument and it just saves this to the output-stream using the right content-type (for me).

In addition I need the reply to have a XML declaration like <?xml version="1.0" encoding="UTF-8"?> so I added these two helpers (the second giving you a simple clue on the usage):

let respond 
       (fillResult : System.Xml.XmlElement -> unit) 
       request =
    let reply = System.Xml.XmlDocument ()
    reply.CreateXmlDeclaration("1.0", "UTF-8", null) 
    |> reply.AppendChild |> ignore
    
    let element = reply.CreateElement ("Result") 
    fillResult element
    reply.AppendChild element |> ignore

    let msg = new System.Net.Http.HttpResponseMessage ()
    msg.RequestMessage <- request
    msg.Content <- new XmlContent (reply)
    msg

let respondText (text : string) =
    respond (fun element ->
        element.OwnerDocument.CreateTextNode text 
        |> element.AppendChild 
        |> ignore)

the controller

Nothing special here – a simple one like this will do:

type RootController() =
    inherit ApiController ()

    [<HttpGet()>]
    member this.Echo (echo : string) =
        this.Request 
        |> respondText echo

setting up the self-hosting

I use a simple console application for this and so the Program.fs just looks like:

open System
open System.Net
open System.Web.Http

open System.Web.Http.SelfHost

[<CLIMutable>]
type Route = { controller : string; action : string }

[<EntryPoint>]
let main argv = 
    try
        use config = new HttpSelfHostConfiguration
                           ("http://localhost:8083")
        config.Formatters.XmlFormatter.UseXmlSerializer <- true
        config.Routes.MapHttpRoute (
            "default",
            "{action}",
            { controller = "Root"; action = "Echo" } )
        |> ignore

        use server = new HttpSelfHostServer(config)
        server.OpenAsync().Wait()

        printfn "RETURN to quit"
        Console.ReadLine() |> ignore

    with
    | _ as ex ->
        Console.WriteLine ex.Message 

    0

remarks

  • it might not be strictly needed here but in case I don’t use the respond function I added UseXmlSerializer to get a nice way to serialize F#-records and stuff (just add [<CliMutable>]).
  • the try/with is just there to give me a handle to ex – to some reason if the debugger breaks int a AggregateExcpetion VS2013 will no longer show me the detail of it 🙁
  • you will have to run VisualStudio (or the program) as an local administrator – if not the URL rewrite will fail with an AggregateException.