A reusable ApiController Adapter by Mark Seemann
Refactor ApiController Adapters to a single, reusable base class.
Regular readers of this blog will know that I write many RESTful APIs in F#, using ASP.NET Web API. Since I like to write functional F#, but ASP.NET Web API is an object-oriented framework, I prefer to escape the object-oriented framework as soon as possible. (In general, it makes good architectural sense to write most of your code as framework-independent as possible.)
(Regular readers will also have seen the above paragraph before.)
To bridge the gap between the object-oriented framework and my functional code, I implement Controller classes like this:
type CreditCardController (imp) = inherit ApiController () member this.Post (portalId : string, req : PaymentDtr) : IHttpActionResult = match imp portalId req with | Success (resp : PaymentDtr) -> this.Ok resp :> _ | Failure RouteFailure -> this.NotFound () :> _ | Failure (ValidationFailure msg) -> this.BadRequest msg :> _ | Failure (IntegrationFailure msg) -> this.InternalServerError (InvalidOperationException msg) :> _
The above example is a Controller that handles incoming payment data. It immediately delegates all work to an injected imp
function, and pattern matches on the return value in order to return correct responses.
This CreditCardController
extends ApiController in order to work nicely with ASP.NET Web API, while the injected imp
function is written using functional programming. If you want to be charitable, you could say that the Controller is an Adapter between ASP.NET Web API and the functional F# API that actually implements the service. If you want to be cynical, you could also call it an anti-corruption layer.
This works well, but tends to become repetitive:
open System open System.Web.Http type BoundaryFailure = | RouteFailure | ValidationFailure of string | IntegrationFailure of string type HomeController (imp) = inherit ApiController () [<AllowAnonymous>] member this.Get () : IHttpActionResult = match imp () with | Success (resp : HomeDtr) -> this.Ok resp :> _ | Failure RouteFailure -> this.NotFound () :> _ | Failure (ValidationFailure msg) -> this.BadRequest msg :> _ | Failure (IntegrationFailure msg) -> this.InternalServerError (InvalidOperationException msg) :> _ type CreditCardController (imp) = inherit ApiController () member this.Post (portalId : string, req : PaymentDtr) : IHttpActionResult = match imp portalId req with | Success (resp : PaymentDtr) -> this.Ok resp :> _ | Failure RouteFailure -> this.NotFound () :> _ | Failure (ValidationFailure msg) -> this.BadRequest msg :> _ | Failure (IntegrationFailure msg) -> this.InternalServerError (InvalidOperationException msg) :> _ type CreditCardRecurrentStartController (imp) = inherit ApiController () member this.Post (portalId : string, req : PaymentDtr) : IHttpActionResult = match imp portalId req with | Success (resp : PaymentDtr) -> this.Ok resp :> _ | Failure RouteFailure -> this.NotFound () :> _ | Failure (ValidationFailure msg) -> this.BadRequest msg :> _ | Failure (IntegrationFailure msg) -> this.InternalServerError (InvalidOperationException msg) :> _ type CreditCardRecurrentController (imp) = inherit ApiController () member this.Post (portalId : string, transactionKey : string, req : PaymentDtr) : IHttpActionResult = match imp portalId transactionKey req with | Success (resp : PaymentDtr) -> this.Ok resp :> _ | Failure RouteFailure -> this.NotFound () :> _ | Failure (ValidationFailure msg) -> this.BadRequest msg :> _ | Failure (IntegrationFailure msg) -> this.InternalServerError (InvalidOperationException msg) :> _ type PushController (imp) = inherit ApiController () member this.Post (portalId : string, req : PushRequestDtr) : IHttpActionResult = match imp portalId req with | Success () -> this.Ok () :> _ | Failure RouteFailure -> this.NotFound () :> _ | Failure (ValidationFailure msg) -> this.BadRequest msg :> _ | Failure (IntegrationFailure msg) -> this.InternalServerError (InvalidOperationException msg) :> _
At this point in our code base, we had five Controllers, and they all had similar implementations. They all pass their input parameters to their injected imp
functions, and they all pattern match in exactly the same way. There are, however, small variations. Notice that one of the Controllers expose an HTTP GET operation, whereas the other four expose a POST operation. Conceivably, there could also be Controllers that allow both GET and POST, and so on, but this isn't the case here.
The parameter list for each of the action methods also vary. Some take two arguments, one takes three, and the GET method takes none.
Another variation is that HomeController.Get
is annotated with an [<AllowAnonymous>]
attribute, while the other action methods aren't.
Despite these variations, it's possible to make the code less repetitive.
You'd think you could easily refactor the code by turning the identical pattern matches into a reusable function. Unfortunately, it's not so simple, because the methods used to return HTTP responses are all protected
(to use a C# term for something that F# doesn't even have). You can call this.Ok ()
or this.NotFound ()
from a derived class, but not from a 'free' let
-bound function.
After much trial and error, I finally arrived at this reusable base class:
type private Imp<'inp, 'out> = 'inp -> Result<'out, BoundaryFailure> [<AbstractClass>] type FunctionController () = inherit ApiController () // Imp<'a,'b> -> 'a -> IHttpActionResult member this.Execute imp req : IHttpActionResult = match imp req with | Success resp -> this.Ok resp :> _ | Failure RouteFailure -> this.NotFound () :> _ | Failure (ValidationFailure msg) -> this.BadRequest msg :> _ | Failure (IntegrationFailure msg) -> this.InternalServerError (InvalidOperationException msg) :> _
It's been more than a decade, I think, since I last used inheritance to enable reuse, but in this case I could find no other way because of the design of ApiController. It gets the job done, though:
type HomeController (imp : Imp<_, HomeDtr>) = inherit FunctionController () [<AllowAnonymous>] member this.Get () = this.Execute imp () type CreditCardController (imp : Imp<_, PaymentDtr>) = inherit FunctionController () member this.Post (portalId : string, req : PaymentDtr) = this.Execute imp (portalId, req) type CreditCardRecurrentStartController (imp : Imp<_, PaymentDtr>) = inherit FunctionController () member this.Post (portalId : string, req : PaymentDtr) = this.Execute imp (portalId, req) type CreditCardRecurrentController (imp : Imp<_, PaymentDtr>) = inherit FunctionController () member this.Post (portalId : string, transactionKey : string, req : PaymentDtr) = this.Execute imp (portalId, transactionKey, req) type PushController (imp : Imp<_, unit>) = inherit FunctionController () member this.Post (portalId : string, req : PushRequestDtr) = this.Execute imp (portalId, req)
Notice that all five Controllers now derive from FunctionController
instead of ApiController
. The HomeController.Get
method is still annotated with the [<AllowAnonymous>]
attribute, and it's still a GET operation, whereas all the other methods still implement POST operations. You'll also notice that the various Post
methods have retained their varying number of parameters.
In order to make this work, I had to make one trivial change: previously, all the imp
functions were curried, but that doesn't fit into a single reusable base implementation. If you consider the type of FunctionController.Execute
, you can see that the imp
function is expected to take a single input value of the type 'a
(and return a value of the type Result<'b, BoundaryFailure>
). Since any imp
function can only take a single input value, I had to uncurry them all. You can see that now all the Post
methods pass their input parameters as a single tuple to their injected imp
function.
You may be wondering about the Imp<'inp, 'out>
type alias. It's not strictly necessary, but helps keep the code clear. As I've attempted to indicate with the code comment above the Execute
method, it's generic. When used in a derived Controller, the compiler can infer the type of 'a
because it already knows the types of the input parameters. For example, in CreditCardController.Post
, the input parameters are already annotated as string
and PaymentDtr
, so the compiler can easily infer the type of (portalId, req)
.
On the other hand, the compiler can't infer the type of 'b
, because that value doesn't relate to IHttpActionResult
. To help the compiler, I introduced the Imp
type, because it enabled me to concisely annotate the type of the output value, while using a wild-card type for the input type.
I wouldn't mind getting rid of Controllers altogether, but this is as close as I've been able to get with ASP.NET Web API.
Comments
I find a few issues with your blog post. Technically, I don't think anything you write in it is wrong, but there are a few of the statements I find problematic in the sense that people will probably read the blog post and conclude that "this is the only solution because of X" when in fact X is trivial to circumvent.
The main issue I have with your post is the following:
You'd think you could easily refactor the code by turning the identical pattern matches into a reusable function. Unfortunately, it's not so simple, because the methods used to return HTTP responses are all
protected
(to use a C# term for something that F# doesn't even have). You can callthis.Ok ()
orthis.NotFound ()
from a derived class, but not from a 'free' let-bound function.As stated earlier, there is nothing in your statement that is technically wrong, but I'd argue that you've reached the wrong conclusion due to lack of data. Or in this case familiarity with Web API and it's source code. If you go look at the source for
ApiController
, you will notice that while true, the methods areprotected
, they are also trivial one-liners (helper methods) calling public APIs. It is completely possible to create a helper ala:Another thing you can do is implement IHttpActionResult on your discriminate union, so you can just return it directly from you controller, in which case you'd end up with code like this:
It's definitely a bit more work, but in no way is it undoable. Thirdly, Web API (and especially the new MVC Core which has taken over for it) is incredibly pluggable through it's DI. You don't have to use the base
ApiController
class if you don't want to. You can override the resolution logic, the handing of return types, routing, you name it. It should for instance be entirely doable to write a handler for "controllers" that look like this:This however would probably be a bit of work requiring quite a bit of reflection voodoo. But somebody could definitely do it and put it in a library, and it would be nuget-installable for all.
Anyways, I hope this shows you that there are more options to solve the problem. And who knows, you may have considered all of them and concluded them unviable. I've personally dug through the source code of both MVC and Web API a few times which is why I know a bunch of this stuff, and I just figured you might want to know some of it too :).
Aleksander, thank you for writing. I clearly have some scars from working with ASP.NET Web API since version 0.6 (or some value in that neighbourhood). In general, if a method is defined on
ApiController
, I've learned the hard way that such methods tend to be tightly coupled to the Controller. Often, such methods even look into the untyped context dictionary in the request message object, so I've gotten used to use them whenever they're present.These scars have prevented me from pushing the envelope on the 'new' methods that return various
IHttpActionResult
objects, but as you write, they truly are thin wrappers over constructors.This does, indeed, enable us to write the mapping of result values as a let-bound function. Thank you for pointing that out!
Mine ended up looking like the following. We've added a few more success and error cases since I originally wrote this article, so it looks more complex than the initial example.
Letting my
BoundaryFailure
discriminated union implementIHttpActionResult
sounds promising, but how would that be possible?The interface has a single method with the type
CancellationToken -> Task<HttpResponseMessage>
. In order to create e.g.NotFoundResult
, you need either anHttpRequestMessage
or anApiController
, and none of those are available whenIHttpActionResult.ExecuteAsync
is invoked.When it comes to not having to derive from
ApiController
, I'm aware that even on the full .NET framework (we're not on .NET Core), 'all' the framework needs areIHttpController
instances. As you imply, plugging into the framework is likely to be non-trivial, and so far, I haven't found that such an effort would be warranted. I might use it if someone else wrote it, though :)