Using a DI container provides some advantages as well as some disadvantages.
The main advantage is that if you have a large code base, you can use the configuration convention to connect all the dependencies. If you agree with the configuration, you will also get the advantage that your code will be more consistent, because it must follow the conventions.
However, there are several drawbacks. The most immediate thing is that you lose fast feedback from the compiler . You can make it much easier to make changes to your system, and while everything compiles, the system crashes at runtime. Some people hope that you can ask the DI Container to diagnose it yourself, but you cannot .
Another, less obvious, drawback of using the DI container is that it becomes too easy, as you say, to simply add more members to the controllers, etc. This actually increases traction or decreases traction, but the reflection-based automation provided by DI Containers hides this problem from you.
Since I believe the disadvantages outweigh the benefits, I recommend that you use Pure DI instead of DI containers.
This applies to object-oriented programming in C #, Visual Basic.NET or Java, but equally applies to functional programming in F #.
In the unbiased functional base of F # code, I would not use classes or interfaces at all; instead, I only collected functions.
In a mixed codebase, such as the ASP.NET Web API, I would move the bridge between OOP and FP as quickly as possible. As I explain in the section Test-Driven Development with F # ( advanced material available on Pluralsight ), d will inject the function into the controller as follows:
type ReservationsController(imp) = inherit ApiController() member this.Post(rendition : ReservationRendition) : IHttpActionResult = match imp rendition with | Failure(ValidationError msg) -> this.BadRequest msg :> _ | Failure CapacityExceeded -> this.StatusCode HttpStatusCode.Forbidden :> _ | Success () -> this.Ok () :> _
This is the entire code base of this Controller. All behavior is implemented by imp .
In the application startup code, imp is like this:
let imp = Validate.reservationValid >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats) >> Rop.map SqlGateway.saveReservation
You can argue that the ReservationsController above defines only one Post method. What if the controller should expose more methods?
In this case, add an implementation function for each method. In the REST API, any controller should have 2-3 methods in any case, which means, in fact, 2-3 dependencies. In my opinion, this is a perfectly acceptable number of dependencies.
The reason for the maximum of method 2-3 is that in a proper RESTful design, resources tend to follow several interaction patterns:
GETPostPost GETPUT , GETDELETE , GETPUT , DELETE , GET
The full combination ( Post , GET , PUT , DELETE ) is the smell of REST design, but it's all a completely different discussion.