Service locator free architecture with Ninject WCF Extenstion
I finally got some understanding of how Ninject handles DI, but have faced the following problem:
Let's consider we have a class that takes two WCF ServiceHost objects as a constructor parameters:
public ActivitySinkServer(IData开发者_开发技巧Provider dataProvider, ServiceHost posClients, ServiceHost activitySinkOperatorClients)
At first I had only one ServiceHost dependency, so I easily have handled the binding like this:
public class CommunicationModule: NinjectModule
{
public override void Load()
{
Bind<POSClient>().ToSelf().WithConstructorArgument("posManager", Kernel.Get<POSManager>());
this.Bind<ServiceHost>().ToMethod(ctx => ctx.Kernel.Get<NinjectServiceHost>(new ConstructorArgument("singletonInstance", c => c.Kernel.Get<POSClient>())));
}
}
In this scenario my ActivitySinkServer
could resolve it's ServiceHost
dependency with a NinjectServiceHost
initialized with a singleton object.
Now, that I have two ServiceHost dependencies, how can I tell Ninject which one to feed at which constructor parameter, still having my inner code Ninject-unaware. (I know I could have used Ninject attributes and other stuff from the manual).
UPDATE:
I went ahead and just used
.When(request => request.Target.Name == "posClients");
.When(request => request.Target.Name == "activitySinkOperatorClients");
to explicitly specify the target constructor variable names. Don't see any harm in that. However, if someone has a more elegant and object-oriented aproach - you are welcome to answer.
The way you're doing it is 100% fine; the more "elegant" way of doing it is to use named bindings or metadata.
By the way, it's far better not to use the singletonInstance
constructor of ServiceHost
in this case, because if you initialize it that way then WCF will not allow you to use any other instancing mode (such as PerCall
). Let Ninject and WCF handle the instancing and use the type-based constructor instead.
A named binding example would be:
class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<ServiceHost>().To<NinjectServiceHost>().Named("POS")
.WithConstructorArgument("serviceType", typeof(PosService))
.WithConstructorArgument("baseAddresses", new Uri[0]);
Bind<ServiceHost>().To<NinjectServiceHost>().Named("ActivitySink")
.WithConstructorArgument("serviceType", typeof(ActivitySink))
.WithConstructorArgument("baseAddresses", new Uri[0]);
}
}
public class Server
{
private readonly ServiceHost posHost;
private readonly ServiceHost activitySink;
public Server(IDataProvider dataProvider,
[Named("POS")] posHost,
[Named("ActivitySink")] activitySink)
{
this.posHost = posHost;
this.activitySink = activitySink;
}
}
Note that initializing the baseAddresses
constructor argument is necessary for Ninject to choose the correct overload. Initializing it to new Uri[0]
specifically just causes the default behaviour of looking in app.config
to find the base addresses, so don't worry about passing in an empty array.
Although this couples your Server
class to Ninject itself, generally your ServiceHost
instances are created in the application binary and not a library, so the coupling is a non-issue.
I prefer this approach to the When
syntax because it's less likely to break during a refactoring. It's not unlikely that somebody will one day decide to change the constructor parameter names, and there is no visual indication whatsoever that anything is depending on those names, nor is there any way for Visual Studio's automated refactoring to detect that dependency.
So, IMO, it's better to make the dependency explicit here using attributes; that way if somebody decides to add a third service host later on, they'll know instantly that they need to add an attribute and update the corresponding Ninject module.
精彩评论