5

Closed

Separate service location from dependency injection

description

There is a lot of confusion today about how Web API uses service location vs. dependency injection, caused in no small part by the fact that both activities today are performed against IDependencyResolver.

Service location is about creating objects which are only created once for the lifetime of the configuration and then generally cached for further use. Dependency injection is used to create objects which have a transient lifetime (typically a per-request lifetime, though this is not necessarily dictated in the contract of the dependency resolver).

Service location will live in a new property, HttpConfiguration.Services, which is not user pluggable. This service list will be populated with the known default services present in the application, and the application developer can add and remove services from this list. These services are considered to be "global" to the configuration, and the single instance of these services is shared across all requests. Only known service types will be gettable or settable in the service locator.

Dependency injection will continue to use IDependencyResolver, which will be user pluggable. Web API itself will only use dependency injection (directly) for one type of object creation in this version: for controller creation. IDependencyResolver will get a new Release method, which will be used to return any objects that were obtained from IDependencyResolver, once the framework is done using them. In addition, by making IDependencyResolver the known "one place" for dependency injection, this will allow us to add future DI-able types to the framework without rework, as well as allowing application/component developers to rely upon a single place to get transient objects.

By default, the first time a service is requested from the service locator, it will consult the dependency resolver, which means that a user-registered DI container has an opportunity to provide instances of those core Web API services. For calls to GetService(), the DI container service will take precedence over the default in the service locator; for calls to GetServices(), the DI container results will be prepended to the the results from the service resolver. The services that the service locator gets from DI will be held onto for the lifetime of the configuration, and then released when the configuration is disposed.
Closed Apr 26, 2013 at 8:14 AM by hongyes

comments

ploeh wrote Apr 4, 2012 at 8:22 AM

The above distinction between Service Location and Dependency Injection is fundamentally flawed. SL and DI are two mutually exclusive approaches to enable loose coupling: http://www.infoq.com/articles/Succeeding-Dependency-Injection

Lifetime management doesn't even enter the picture.

ploeh wrote Apr 4, 2012 at 8:35 AM

Ten minutes later I realize that the above comment may have come across in a less that constructive tone, for which I apologize. It must be the 'passion' speaking - it's still one of the core Microsoft values, I believe :)

In any way, now that you are open source and take contributions, I'd love to help if you'd be interested. Please let me know if this is the case.

BradWilson wrote Apr 5, 2012 at 2:49 AM

Fixed in snapshot 1dcec5dfecea79433c7052dd86875e26256c15f8.

ploeh, please review the source and see what you think of the changes we've made to delineate built-in services from DI.

henric wrote Apr 7, 2012 at 10:55 PM

The last comment in src/System.Web.Http/Dependencies/IDependencyScope.cs uses a non-traditional spelling: "isntances".

ploeh wrote Apr 9, 2012 at 9:55 AM

Sorry about the late response, but you hit me just during the Easter vacation, which we in this ungodly land of ours (Denmark) celebrate with much fervor (well, at least we take the time off...)

I'm mostly basing this comment on a summary of the changes I received from Henrik via email - I haven't yet had time to do a detailed inspection of the code base itself.

First of all, I'm happy to see the inclusion of a Release hook of sorts. That's absolutely the most important of all the feedback I originally had (http://blog.ploeh.dk/2012/03/20/RobustDIWithTheASPNETWebAPI.aspx if any readers arrive at this discussion out of context).

ON DEPENDENCY SCOPES
You've chosen to implement this with a custom scope (IDependencyScope), which is one well-known way to model dependency lifetimes. It has advantages and disadvantages.

Conceptually, it's very easy to understand, as an HTTP request defines a very intuitive scope. This makes a lot of sense. When it comes to the lifetime of an HttpConfiguration, that may also work - I guess it's basically a scope that lasts for the lifetime of the application, but tying a scope to a specific HttpConfiguration instance sounds like it opens up the possibility of having more than one HttpConfiguration instances hosted side-by-side in the same process? That sounds like a good thing.

From a framework point of view, modeling lifetimes as scopes makes a lot of sense. From a container viewpoint, whether or not this is the best option depends on which container one wishes to use.

(From a strict container viewpoint, an explicit Release method is superior to lifetime scopes because it opens up for more options when it comes to custom lifetimes. This discussion is perpendicular to the current discussion, but for more details, you can consult my book (http://affiliate.manning.com/idevaffiliate.php?id=1150_236) and read the chapters about custom lifetimes for respectively Castle Windsor and Autofac - the latter chapter (13) basically describes how lifetime scopes is a rather constrained model for extensibility. However, that's all from the viewpoint of a container, so it's not so important from a framework viewpoint, because if you ever decide you need to define a new scope, you can just add one. This whole aside just explains why I have a slight preference for explicit Release methods and the Register Resolve Release pattern (http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasePattern.aspx))

How easy would it be to adopt a particular DI Container to IDependencyScope?
  • Unity supports lifetime scopes, so that should be trivial. Ironically, Unity also sort of supports RRR, but only partially.
  • Autofac supports lifetime scopes, so that should be trivial.
  • Castle Windsor doesn't support lifetime scopes, so implementing IDependencyScope around IWindsorContainer is going to be harder. On the other hand, since the decommissioning hook (Dispose) is present, it would be possible to create a reusable custom lifetime that respects this scope. That's basically what the built-in PerWebRequest lifestyle already does.
  • MEF, last time I checked, only supports the RRR pattern, but I'm not completely up to date here. However, implementing IDependencyScope with MEF might not be a trivial undertaking, but I don't regard it as impossible either.
  • StructureMap doesn't support decommissioning at all, so whether the framework models decommissioning along lifetime scopes or RRR, it doesn't matter.
  • Spring.NET doesn't support decommissioning either.
However, if you were to switch around and model decommissioning in terms of the RRR pattern, while you would make it easier to use Castle Windsor and MEF, you'd make it a bit harder to use Autofac (and partially also Unity), so it's a tradeoff.

ON SEPARATION OF FRAMEWORK AND USER SERVICES
As far as I can tell, you've also gone to some lengths to separate framework services (such as IHttpControllerActivator and the like). I'm not quite sure what exactly that's supposed to achieve...

I actually did like that there was a universal hook into the inner workings of the framework. The ability to intercept core building blocks of the framework is very powerful, and I think it should remain like that.

My original main gripe was that the parts of the framework that use those services don't trust the DependencyResolver/DependencyScope to manage the lifetime of the services. Containers are very good at managing lifetime, so you should trust them completely.

Things would be a lot simpler if the framework made no distinction at all between well-known services and custom services. Just request an instance of the type you need and use it. No need to cache it for later use - the scope/container can do that if it's necessary. This is called the Singleton lifestyle - see chapter 8 of my book).

I actually had a scenario where it would have been really helpful to define a IHttpControllerActivator in a per-request lifetime scope, but it turned out that I couldn't do that because the framework cached the instance. The framework would have been a lot more powerful if it hadn't done that, and no less efficient in its default configuration.

ploeh wrote Apr 20, 2012 at 2:27 PM

BTW, for a motivating example of why it's a bad idea to give special treatment to framework services, see this post: http://blog.ploeh.dk/2012/04/19/WiringHttpControllerContextWithCastleWindsor.aspx

castelobranco wrote Jul 24, 2012 at 2:26 AM

I have not find the new Release method on the interface IDependencyResolver on source code. Did you change your mind?