F# on aspnetcore: Escaping the framework
February 15, 2017 @ 2:24 pm Posted to .Net, dotnetcore, F# by Antony KochMark Seemann has both blogged and talked about escaping the OO .Net Web API framework in order to use a more idiomatic functional style. This is achieved by providing a function per verb to the controller’s constructor, and replacing the IHttpControllerActivator:
type CompositionRoot() =
interface IHttpControllerActivator with
member this.Create(request, controllerDescriptor, controllerType) =
if controllerType = typeof<HomeController> then
new HomeController() :> IHttpController
elif controllerType = typeof<DoesSomethingController> then
let imp x = x * x
let c = new DoesSomethingController(imp) :> _
else
raise
<| ArgumentException(
sprintf "Unknown controller type requested: %O" controllerType,
"controllerType")
Then in the startup for your app (global or Startup):
GlobalConfiguration.Configuration.Services.Replace(
typeof<IHttpControllerActivator>,
CompositionRoot(
reservations,
notifications,
reservationRequestObserver,
seatingCapacity))
This works great, and I love its honesty. It makes you feel the pain, to quote Greg Young, and in composing tight workflows in your composition root the ‘what’ of your domain is laid bare.
However, this won’t work in aspnetcore because it’s more Mvc and less WebApi, or – to use MS phrasology – more Web and less Http, meaning there’s no IHttpControllerActivator. The fix is simple, and aligned with the terminology: drop the ‘http!’ One instead replaces the IHttpControlleractivator with an IControllerActivator instance inside the aspnetcore DI framework and the same results are achieved:
type CustomControllerActivator() =
interface IControllerActivator with
member this.Create(c : ControllerContext) : obj =
if c.ActionDescriptor.ControllerTypeInfo.AsType() =
typeof<DoesSomethingController> then
let imp x = x * x
new DoesSomethingController(imp) |> box
else
invalidArg "controllerType" "Cannot find controller"
member this.Release (c : ControllerContext, ctrl : obj) =
()
And in your OWIN startup:
member this.ConfigureServices (services:IServiceCollection) =
services.AddSingleton<IControllerActivator>(new CustomControllerActivator()) |> ignore
services.AddMvc() |> ignore
Sorted!