Creating an F# ASP .Net Core application (Part 2)

Tags: fsharp, code, learning, netcore,

I am little masochist and I am trying to build an ASP .Net Core application in F#. This should be possible as F# is a .Net language and it is “supported” in .Net Core but, as there are no much real support in Visual Studio and not much information on the Web, it is proving to be harder and uglier than I original expected. The first part of this series is here.

After having the project created with dotnet new -l fs, the first thing I did was to update in the projet.json file the version of the Microsoft.FSharp.Core.netcore from 1.0.0-alpha-160629 to 1.0.0-alpha-160831 which is the latest version of this package. I hope that at some point I will be able to update to a release version.

Then, lets change the main function from the simple hello world to an application that creates and runs a webhost.

open Microsoft.AspNetCore.Hosting

[<EntryPoint>]
let main argv =
    let host = WebHostBuilder()
                .Build()

    do host.Run()
    0 // return an integer exit code

Notice that the class WebHostBuilder belongs to the namespace Microsoft.AspNetCore.Hosting and this namespace is included in the assembly package Microsoft.AspNetCore.Server.Kestrel. So we need to include this package as a dependency in the project.json file.

{
  [...]
  "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1"
  }
}

And, although the project does not show any errors when executing dotnet build, it fails when executing dotnet run with the following exception (which basically says that we haven’t provided a startup class):

Unhandled Exception: System.InvalidOperationException: No service for type 'Microsoft.AspNetCore.Hosting.IStartup' has been registered.
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureStartup()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at Program.main(String[] argv)

To fix this error we will need to provide a Startup class with an empty Configure method.

The Configure method needs to accept an IApplicationBuilder parameter, and this interface lives on the Microsoft.AspNetCore.Hosting namespace. Also, the Startup class has to be registered on the WebhostBuilder.

open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting

type Startup(env: IHostingEnvironment) =
    member x.Configure(app: IApplicationBuilder) =
        ()

[<EntryPoint>]
let main argv =
    let host = WebHostBuilder()
                .UseStartup<Startup>()
                .Build()

    do host.Run()
    0 // return an integer exit code

And if you execute dotnet run again, now the exception changed to (server is not registered):

Unhandled Exception: System.InvalidOperationException: No service for type 'Microsoft.AspNetCore.Hosting.Server.IServer' has been registered.
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureServer()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at Program.main(String[] argv)

Adding a call to the UseKestrel extension method does the job

    let host = WebHostBuilder()
                .UseKestrel()
                .UseStartup<Startup>()
                .Build()

and executing dotnet run creates and executes a new kestrel web server that does nothing.

Lets make it do something before finish this blog post. The simplest thing a server can do is write a hardcoded response to every request. We can do that on the Configure method of the Startup class.

type Startup(env: IHostingEnvironment ) =
    member x.Configure(app: IApplicationBuilder) =
        do app.Run(fun context ->
            async {
                do! context.Response.WriteAsync("Hi!") |> Async.AwaitIAsyncResult |> Async.Ignore
            } |> Async.StartAsTask :> Task
        )

A dependency to Microsoft.AspNetCore.Http has to be included because the WriteAsync extension method is defined in this package so lets include it.

  "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.AspNetCore.Http": "1.0.0"
  }

The uggliness of this method comes from the call to WriteAsync. We need to await the result but the result will be void (because WriteAsync returns a Task) so we just ignore it.

There are some ways to make this look nicer like this awaitTask function

open Microsoft.AspNetCore.Http
open System.Threading.Tasks

let awaitTask = Async.AwaitIAsyncResult >> Async.Ignore

type Startup(env: IHostingEnvironment ) =
    member x.Configure(app: IApplicationBuilder) =
        do app.Run(fun context ->
            async {
                do! awaitTask (context.Response.WriteAsync("Hi!"))
            } |> Async.StartAsTask :> Task
        )

It still looks ugly but just a little better.