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 addedUseXmlSerializer
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 toex
– to some reason if the debugger breaks int aAggregateExcpetion
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
.