Ninjecting Dependencies Across Unreferenced Assemblies

269 views
Skip to first unread message

Brian Chavez

unread,
Aug 1, 2008, 7:55:26 PM8/1/08
to ninject
Hi,

I'm trying to figure out ways to use Ninject without having to
reference the types inside the assemblies themselves.

For example:

Project.Core
>IADao

Project.Data
>ADao : IADao

Project.Run:
>Uses Ninject to resolve IADao.

Project.Run is only allowed to reference Project.Core. This is a
requirement.

This means, I cannot reference Project.Data, in turn, I cannot setup
Ninject bindings located in Project.Run, because the binding service
type must be resolvable at compile time in order for me to use Bind<
IADao >.To< _ADao_ >().

I noticed, there is a module called AutoModule that scans assembly
types for the Service attribute that would auto-registers the bindings
given an assembly name. This is nice, and will work; however, suppose
I have a specialized assembly that overrides the the default
Project.Data.

For example:
Project.SpeicalizedData
>SpeicalDataAccess : IADao

SpeicalDataAccess implements the same IADao as Project.Data.ADao, but
I want to be able to tell Ninject that it's okay to have two services
that implement the same interface, just give SpeicalDataAccess
preference whenever IADao is requested.

Is there a way to do this?

Thanks,
Brian Chavez

fquednau

unread,
Aug 2, 2008, 6:51:17 AM8/2/08
to ninject
What I have been doing is placing a class inheriting from the
StandardModule in any assembly that would want to set up bindings and
just have a little routine that parses all types of all loaded
assemblies and instantiates those deriving from said class. In LINQ it
looks something like this...

var allModules = for a in Assembly.GetAssemblies()
for t in a.GetTypes()
where t.Base == typeof(StandardModule)
&& !("In the Ninject assemblies themselves")
select Activator.CreateInstance(t);

Be aware that your assembly may not be loaded the moment you search,
as it will be loaded once a type from it is requested. I tend to load
all assemblies lying in my resolve path, before looking for paths.

Regards,
Frank

David Kirkland

unread,
Aug 3, 2008, 2:33:59 PM8/3/08
to ninject
@fquednau: I like this idea! I have modified it a little, here's how:

Working on the premise that I may not want to load all modules in a
dll (I'd rather be explicit), I created a new interface:

using Ninject.Core;

namespace MyProject.Common.NinjectIoc
{
public interface INinjectModulesToLoad
{
IModule[] RequiredModules { get; }
}
}

Then I created a 'ninject module autoloader' interface and class:

namespace MyProject.Common.NinjectIoc
{
public interface INinjectModuleAutoLoader
{
void InitAndLoadModulesForLoadedAssemblies();
}
}



using System;
using System.Collections.Generic;
using System.Reflection;
using Ninject.Core;

namespace MyProject.Common.NinjectIoc
{
public class NinjectModuleAutoLoader : INinjectModuleAutoLoader
{
private readonly IKernel kernel;

public NinjectModuleAutoLoader(IKernel kernel)
{
this.kernel = kernel;
}

public void InitAndLoadModulesForLoadedAssemblies()
{
foreach (Assembly a in
AppDomain.CurrentDomain.GetAssemblies())
{
LoadModulesFromAssembly(a);
}

AppDomain.CurrentDomain.AssemblyLoad +=
new
AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
}

private void LoadModulesFromAssembly(Assembly assembly)
{
foreach (IModule[] modules in
FindAllModulesToLoad(assembly))
{
kernel.Load(modules);
}
}

private IEnumerable<IModule[]> FindAllModulesToLoad(Assembly
assembly)
{
foreach (Type t in assembly.GetTypes())
{
if (!t.IsAbstract && t.IsPublic &&

t.GetInterface(typeof(INinjectModulesToLoad).FullName) != null)
{
INinjectModulesToLoad modulesToLoad =
(INinjectModulesToLoad)Activator.CreateInstance(t);
yield return modulesToLoad.RequiredModules;
}
}
}

private void CurrentDomain_AssemblyLoad(object sender,
AssemblyLoadEventArgs args)
{
LoadModulesFromAssembly(args.LoadedAssembly);
}
}
}

The AssemblyLoad event is now monitored and any assemblies that load
in the future should also be caught.

Each assembly now has at least one class which implements
INinjectModulesToLoad.

And to kick-start the process:

IKernel kernel = new StandardKernel();
INinjectModuleAutoLoader moduleAutoLoader = new
NinjectModuleAutoLoader(kernel);
moduleAutoLoader.InitAndLoadModulesForLoadedAssemblies();

Now I don't need to reference my data access layer from my service
layer!

ONce again, thanks fquednau for planting the seed :-)

Simone Chiaretta

unread,
Aug 3, 2008, 3:50:22 PM8/3/08
to nin...@googlegroups.com
I agree that this la a cool way ho load modules. Ninject misses the
xml config file but this kind of autodisco is on the way to make it
useless.
Simo

From my iPhone


--
Simone Chiaretta
codeclimber.net.nz
Any sufficiently advanced technology is indistinguishable from magic
"Life is short, play hard"

Brian Chavez

unread,
Aug 3, 2008, 4:13:15 PM8/3/08
to ninject
Hi fquednau,

Thanks for the reply.

But I think I'm still stuck on figuring out how to try and avoid
multiple default bindings when loading types from the assemblies.

I get how you can load custom bindings through IModules packed with
the assembly. But, I don't think it solves the problem of having two
IModules that declare the same service type interface to two different
backing implementations.

For example, two assemblies A and B, reference IDao in Assembly C.
You could potentially have, AModule Bind(IDao).To(A), and
BModule.Bind(IDao).To(B).

If you were to execute and load both AModule and BModule, you'll now
have a conflict with Ninject because there are two default bindings to
the same service type. From what I have studied, multiple default
bindings are not allowed in Ninject.

So, the root of my question is how can I replace any previous default
bindings in an elegant/maintainable way? How do you resolve issues
like this?

I could be totally wrong, or way off, please let me know.

Thanks,
Brian Chavez

Brian Chavez

unread,
Aug 3, 2008, 8:23:55 PM8/3/08
to ninject
Ok, well, I think I answered my own question...

As far as I can see, it looks like the only reasonable way to avoid
multiple bindings would be to roll your own IModule that fiddles with
the Kernel.Components.Get< IBindingRegistry > k-component during the
loading of your assembly types to check if an existing binding
exists. If an existing default binding exists, come up with some
priority model that whacks out the default binding and replaces it as
needed.

Let me know if there is someone out there that has a better way to
resolve multiple default bindings.

Many thanks,
Brian Chavez

fquednau

unread,
Aug 4, 2008, 5:51:18 AM8/4/08
to ninject
Hi Brian,
not sure about the default bindings, but either way it is important to
your issue to be clear about which binding should take preference.
Maybe your problem is resolved by using conditional bindings. Also
consider by whatever scheme you use to autoload modules to e.g. add
some priority property that will order the modules the way you would
like them to define bindings.

I am not sure how Ninject behaves when you define an already existing
default binding (exception?) but that way you could load first the one
you'd definitely want to have as default binding and in any subsequent
module catch the kind of exception that may be thrown.

hth

David Kirkland

unread,
Aug 4, 2008, 7:39:35 AM8/4/08
to ninject
> I am not sure how Ninject behaves when you define an already existing
> default binding (exception?) but that way you could load first the one
> you'd definitely want to have as default binding and in any subsequent
> module catch the kind of exception that may be thrown.

I think an exception is thrown when you attempt to resolve a type with
duplicate bindings.

Nate Kohari

unread,
Aug 4, 2008, 7:51:17 AM8/4/08
to nin...@googlegroups.com
Brian:

Sorry that I'm late to the party. :) It seems like you could accomplish what you're trying to do simply by defining the "default" bindings in one module (DefaultDaoModule), and the "special" bindings in another module (SpecialDaoModule). I would imagine the decision to use the "default" or the "special" DAO layer would come from some sort of external state or configuration -- so when you're starting your application, just decide at that point to load one module or the other.

There currently is no way to define multiple default bindings for a type. However, you could take advantage of the conditional binding system and define your own ICondition that would evaluate the state of the application and decide whether a "default" or "special" DAO should be returned:

public class UseSpecialDaoCondition : ICondition<IContext> {
  public bool Matches(IContext context) {
    // Return true if you want to use the special DAO.
  }
}

Bind<IDao>().To<DefaultDao>(); // default binding
Bind<IDao>().To<SpecialDao>().Only(new UseSpecialDaoCondition()); // conditional binding

You could actually define these bindings in separate modules -- for example, the default binding could go in the DefaultDaoModule, and the conditional one could go in the SpecialDaoModule. I suppose a good addition to Ninject would be support for automatically loading all *modules* defined in an assembly -- much like how the AutoModule scans an assembly for services and registers them.

You're off the beaten path quite a bit when it comes to Ninject, so I'm curious to see what sort of creative solution you come up with. :) Hopefully this gets you a bit closer.


-Nate
Reply all
Reply to author
Forward
0 new messages