Ivan Porto Carrero

IO(thoughts) flatMap (_.propagandize)

15

Mar
2009

Ninject Knows a New Trick

Earlier this week Nate already said that I was doing some work on Ninject, now I have it working :). Everything I’m about to talk about is currently in the master tree of the ninject github repository. Getting IronRuby to play nice with Ninject was surprisingly easy :).

There was only one place that required some kind of weird workaround and from that workaround I’m entirely sure that it will go away by the time .NET 4.0 will be here. The DLR duplicates a number of delegates from .NET 4.0 but .NET 3.5 also defines them (i.e. System.Func<T, TT>) and then you get great exception messages like: System.Func is not of type System.Func. The solution is to not reference System.Core in your project. Except that Ninject expects the System.Core variant at some point and that was solved by aliasing the System.Core assembly and talking to the types in that assembly by their alias.

Anyway the juicy stuff :) How can you take advantage of Ninjects newly found friendship with IronRuby.

Ninject now has 2 flavors of Kernels. We have a StandardKernel that knows how to deal with the module configuration system that uses a fluent interface defined in C#. And now we also have a DlrKernel that extends the StandardKernel with a RubyModuleLoader plugin. If you tell the DlrKernel to look inside a path for configuration files it will scan those folders for .dll or .rb files. Those files should contain the configuration for the ninject bindings.

So to create a Kernel that is ruby enabled you would use the following code:

IKernel kernel = DlrKernel();
kernel.AutoLoadModulesRecursively();

var samurai = kernel.Get<IWarrior>();
System.Console.WriteLine(samurai.Weapon.Name);

The above snippet could then for example load a configuration file that has been defined like this:

require File.dirname(__FILE__) + '/../Ninject.Tests.dll'
include Ninject::Tests::Fakes

to_configure_ninject do |ninject|
  ninject.bind IWeapon, :to => Sword
  ninject.bind IWarrior, :to => Samurai
end

The configuration above shows how most of a typical configuration would be defined by you the full configuration API at your disposal. All the options for the configuration can be specified in 2 ways. The first way is in a hash like syntax and the second way uses a more fluent syntax.

to_configure_ninject do |ninject|

  ninject.bind IServiceA, :to => ServiceA, :as => :singleton,
                          :meta => { :type => "superservice" },
                          :name => "aaaaa",
                          :with => {
                            :parameter => { :my_param => lambda { |context| "param_value" } },
                            :constructor_arguments => {:const_arg => 56 },
                            :property_values => {:property_name => 94 },
                          },
                          :on_activation => lambda { |obj| obj.do_some_work },
                          :on_deativated => lambda { |obj| obj.do_some_cleanup },
                          :when => lambda { |context| "a value" } # or
                          # :when => { :injected_into => ServiceB } or
                          # :when => { :target_has => AnAttribute } or
                          # :when => { :member_has => AnAttribute } or
                          # :when => { :class_has => AnAttribute }
  end

Or

to_configure_ninject do |ninject|

  ninject.bind IServiceA, :to => ServiceA, :as => :singleton do
    meta :type => "superservice"
    name "aaaaa"

    with :parameter => { :my_param => lambda { |context| "param_value" } }
    with :constructor_arguments => { :const_arg => 56 }
    with :property_values => { property_name => 94 }

    on_activation do |obj|
      obj.do_some_work
    end

    on_deativation { |obj| obj.do_some_cleanup }

    condition do |context|
      true
    end

    # or

    condition :injected_into => SomeClass

    # or ...

  end

end

Some of the nicer consequences of using Ruby as a configuration language is the syntax for open generics. The example below shows how to configure types with open generics.

require File.dirname(__FILE__) + '/../Ninject.Tests.dll'
include Ninject::Tests::Fakes
include Ninject::Tests::Integration::StandardKernelTests

# IGeneric is a generic interface and GenericService is a generic type
# we don't have to specify any special notation for open generics

to_configure_ninject do |ninject|
  ninject.bind IGeneric, :to => GenericService, :as => :transient
  ninject.bind IGeneric, :to => GenericService2
end

To specify a condition the syntax would look like this

require File.dirname(__FILE__) + '/../Ninject.Tests.dll'
include Ninject::Tests::Fakes

to_configure_ninject do |ninject|
  ninject.bind IWeapon, :to => Shuriken do
    condition do |request|
        request.target.nil?
             ? false
             : request.target.member.reflected_type == Samurai.to_clr_type
      end
  end
  ninject.bind IWeapon, :to => Sword
  ninject.bind IWarrior, :to => Samurai
end

Well that’s all. I hope you like it. I will be looking into more ways to integrate DLR stuff into Ninject the most obvious is allowing you to inject dynamic types into static classes.

Comments

To top