Dependency Injection mit Ninject

ninjectlogo Was Dependency Injection (DI) ist, möchte ich an dieser Stelle nicht mehr erläutern. Dass es jeder braucht auch nicht :) Ninject ist ein kostenloses DI-Framework auf Grundlage des .NET Frameworks.

Im Gegensatz zu dem bekannten Spring.NET wird die Konfiguration bei Ninject nicht über XML Files sondern direkt im Code durchgeführt.

Die Vorteile einer Konfiguration im CLR Code liegen klar auf der Hand

  1. Robustheit (Compile-Time-Check der Konfiguration)
  2. Performance (Verwendung von Code Generation im Gegensatz zu Reflection)
  3. Einfachheit (leicht erlernbar)
  4. Lesbarkeit (Mappings sind in der Hauptanwendungssprache geschrieben)

Ein einfaches Beispiel

Um Ninject verwenden zu können muss eine Referenz zur Assembly Ninject.Core.dll hinzugefügt werden. Im Anschluss daran muss der Namespace Ninject.Core noch durch eine entsprechende using Anweisung verfügbar gemacht werden

using Ninject.Core;

Um eine einfaches Ninject Beispiel zu erstellen benötigt man zunächst einige Contracts, die man später “injecten” möchte.

#region interfaces
 
// interface for addresses
public interface IAddress
{
    String PoBox { get; set; }
    bool HasPoBox();
    void PrintPoBox();
}
//interfaces for media devices
public interface IMediaDevice
{
    void PrintAddress();
}
 
#endregion

 

Natürlich auch noch ein paar Implementierungen der Interfaces.

#region Implementations
 
// example implementation for IMediaDevice
public class WindowsPhone : IMediaDevice
{
    // custom property...
    public String IpAddress { get; set; }
    // interface method
    public void PrintAddress()
    {
        Console.WriteLine(
            String.Format("Your Windows Phone IP address is {0}"
            , this.IpAddress));
    }
}
 
public class IPhone : IMediaDevice
{
    public void PrintAddress()
    {
        Console.WriteLine(
            @"Your IPhone doesn't need an IP address,
            it is powerfull as Chuck Norris! 
            So if you try to reach it, 
            it will give you a roundhouse kick :D");
    }
}
 
// example implementation for IAddress
public class NationalAddress : IAddress
{
    public String PoBox { get; set; }
    public bool HasPoBox()
    {
        return !String.IsNullOrEmpty(this.PoBox);
    }
 
    public void PrintPoBox()
    {
        if (HasPoBox())
        {
            Console.WriteLine(String.Format("PoBox is specified to '{0}'", this.PoBox));
            return;
        }
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("PoBox is not specified");
        Console.ResetColor();
    }
}
 
public class InternationalAddress : IAddress
{
    public string PoBox
    {
        get;
        set;
    }
 
    public bool HasPoBox()
    {
        return false;
    }
 
    public void PrintPoBox()
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(
        @"PoBoxes aren't supported for international addresses");
        Console.ResetColor();
    }
 
}
#endregion

 

Bis hierin ist alles noch Standard, noch ist von Ninject nichts zu sehen. Was nun noch an Datenmodellen fehlt ist ein Consumer, jemand der mit den Interfaces (bzw. später mit Instanzen der Implementierungen) arbeitet.

#region consumer...
 
// simple class
public class Customer
{
    public IAddress MainAddress { get; set; }
    public int Id { get; set; }
    //property injection
    [Inject]
    public IMediaDevice MediaDevice { get; set; }
 
    //Constructor Injection
    [Inject]
    public Customer(IAddress address)
    {
        this.MainAddress = address;
    }
 
    public bool HasPoBox()
    {
        return MainAddress.HasPoBox();
    }
 
    public void DisplayMediaDeviceAddress()
    {
        MediaDevice.PrintAddress();
    }
}
 
#endregion

 

Eine einfache Klasse die die beiden Interfaces verwendet… Und! Zum ersten mal Ninject, wie man sieht ist Ninject wirklich sehr schmal, es wird lediglich über das Inject Attribut angegeben, dass im nachfolgenden Codeblock eine Injection vorgenommen werden soll. Mehr nicht.

Die vier Dependency-Injection Patterns von Ninject

Ninject unterstützt vier verschiedene Arten von Dependency Injection, mit dessen Hilfe man lose Kopplungen in .NET Applikationen realisieren kann.

Constructor Injection

Bei der Constructor Injection versucht Ninject, sofern im Mapping definiert, die vorhandenen Abhängigkeiten aufzulösen und entsprechend dem Mapping die konkreten Typen zur Laufzeit zu verwenden.

#region DI Patterns
 
public class DependencyInjectionPatterns
{
    [Inject]
    public DependencyInjectionPatterns(IMediaDevice mediaDevice, IAddress address)
    {
        // do something with media device
        // do somethinh with address
    }
}
 
#endregion

 

Property Injection

Der klassische Weg, hierbei werden die Dependencies als Properties der Klasse angelegt und Ninject speist zur Laufzeit den korrekten Typ ein.

#region DI Patterns
 
public class DependencyInjectionPatterns
{
    [Inject]
    public IMediaDevice MyMediaDevice { get; set; }
}
 
#endregion

 

Field Injection

Die Field Injection funktioniert analog zur Property Injection.Auf die chronologische Reihenfolge hat man hierbei keinen Einfluss.

#region DI Patterns
 
public class DependencyInjectionPatterns
{
    [Inject]
    private IAddress businessAddress;
 
}
 
#endregion

 

Method Injection

Ninject speist hierbei die konkreten Typen für die Parameter der Methode ein. Auf die chronologische Reihenfolge hat man hierbei keinen Einfluss.

#region DI Patterns
 
public class DependencyInjectionPatterns
{
    [Inject]
    public String PrintAllInformation(IMediaDevice mediaService, 
      IAddress businessAddress, 
      IAddress homeAddress)
    {
      return String.Format("{0}-{1}-{2}", 
        new object[] { 
         mediaService.ToString(), 
         businessAddress.ToString(), 
         homeAddress.ToString() 
    });
    }
}
 
#endregion

 

 

Soweit wäre das Beispiel fertig, lediglich das Mapping fehlt noch. Um Mappings mit Ninject zu realisieren werden sogenannte “Module” erstellt die später vom Ninject Kernel verarbeitet werden.

 

Ninject Module

Module sind bei Ninject wie bereits gesagt dafür verantwortlich, dass das Framework weiß welche Typen einzuspeisen sind. Im nachfolgenden Listing sind zwei Mapping Module angegeben, um später etwas mit dem “Injecting” spielen zu können

 

#region Ninject Modules
 
public class ProductiveModel : StandardModule
{
    public override void Load()
    {
        Bind<IMediaDevice>().To<WindowsPhone>();
        Bind<IAddress>().To<NationalAddress>();
    }
}
 
public class StagingModule : StandardModule
{
    public override void Load()
    {
        Bind<IMediaDevice>().To<IPhone>();
        Bind<IAddress>().To<InternationalAddress>();
 
    }
}
 
#endregion

 

Ninject Kernel

Der Ninject Kernel ist sozusagen die Schaltzentrale von Ninject, denn dem Kernel wird das zu ladende Module übergeben, so dass der Kernel weiss welche Typen er zur Laufzeit verwenden soll.

Im nachfolgenden Beispiel habe ich einen Wrapper um den Kernel gebaut um parallel beide Module im Speicher zu halten. Nach dem ersten Zugriff werden die Kernels immer aus dem Speicher zurückgegeben

#region Ninject Kernel Loading
 
public class ExampleNinjectKernel
{
    public static readonly ExampleNinjectKernel Instance = 
      new ExampleNinjectKernel();
    private IKernel stagingKernel;
    private IKernel productiveKernel;
 
    private ExampleNinjectKernel()
    {
 
    }
 
    public IKernel GetKernel(bool staging)
    {
        if (staging)
        {
            if (stagingKernel != null)
                return stagingKernel;
            stagingKernel = new StandardKernel(new StagingModule());
            return stagingKernel;
        }
        else
        {
            if (productiveKernel != null)
                return productiveKernel;
            productiveKernel = new StandardKernel(new ProductiveModel());
            return productiveKernel;
        }
    }
}
 
 
#endregion

 

Einbindung in eine .NET Applikation

Mit den bis jetzt gezeigten Bestandteilen lässt sich nun bereits eine lauffähige, lose gekoppelte Anwendung erstellen. Ich habe der Einfachheit wegen eine Consolenanwendung ausgewählt. In der Program Klasse erstelle ich nun einfach die gewünschte Kernel Instanz und kann dann über die angebotenen Methoden fertig “injectete” Objektinstanzen anfragen.

Im nachfolgenden Code ist noch viele Calls auf die Console Klasse enthalten, um auch ein schönes Ergebnis zu bekommen :) lässt man diese weg, ist das ganze Szenario nicht größer als 10-15 Zeilen und somit gut lesbar.

static void Main(string[] args)
{
    Console.Title = "Ninject DI Example...";
    IKernel myKernel = 
       ExampleNinjectKernel.Instance.GetKernel(true);
    Customer exampleCustomer = 
       myKernel.Get<Customer>();
    #region some console actions
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("Print some Customer information...");
    Console.WriteLine("Address is now type of '{0}'", 
      exampleCustomer.MainAddress.GetType().FullName);
    Console.WriteLine("Media Device is now type of '{0}'",
      exampleCustomer.MediaDevice.GetType().FullName);
    Console.WriteLine("");
    Console.ResetColor();
    #endregion
    exampleCustomer.DisplayMediaDeviceAddress();
    exampleCustomer.MainAddress.PoBox = "123";
    exampleCustomer.MainAddress.PrintPoBox();
    #region some console actions
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("");
    Console.WriteLine("Staging customer printed");
    Console.ResetColor();
    Console.WriteLine("");
    #endregion
    myKernel =
       ExampleNinjectKernel.Instance.GetKernel(false);
    exampleCustomer = 
       myKernel.Get<Customer>();
    #region some console actions
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("Print some Customer information...[Productive]");
    Console.WriteLine("Address is now type of '{0}'", 
       exampleCustomer.MainAddress.GetType().FullName);
    Console.WriteLine("Media Device is now type of '{0}'", 
      exampleCustomer.MediaDevice.GetType().FullName);
    Console.WriteLine("");
    Console.ResetColor();
    #endregion
    exampleCustomer.DisplayMediaDeviceAddress();
    exampleCustomer.MainAddress.PoBox = "4711";
    exampleCustomer.MainAddress.PrintPoBox();
    #region some console actions
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine();
    Console.WriteLine("productive customer printed");
    Console.WriteLine("");
    Console.WriteLine("");
    Console.ResetColor();
    Console.WriteLine("Press any key to exit...");
    Console.ReadLine();
    
    #endregion
}

 

Die Ausgabe der Anwendung sollte, wenn ihr alles korrekt implementiert habt so aussehen

Ninject DI output

 

In den kommenden Tagen werde ich noch weitere Posts zum Thema Ninject veröffentlichen. Ein wichtiges und interessantes Thema in Bezug auf Ninject sind Contextual Mappings. Durch den Einsatz von kontextabhängigen Bindings kann sich Ninject als DI-Framework erst richtig entfalten.

 

Fazit

Ich hoffe ich konnte mit diesem Post einigen Leuten eine nette Alternative zu Spring.NET nahebringen. Ich persönlich finde Ninject sehr schön, gerade weil es so schlank ist, eventuell ergibt es sich auch mal, dass ich Ninject in größeren Projekten einsetzen kann um das gesamte Portfolio einmal auszuschöpfen.

 

DotNetKicks-DE Image
Published Freitag, 15. Januar 2010 13:07 von ThorstenHans
Abgelegt unter:

Kommentare

# re: Dependency Injection mit Ninject

Donnerstag, 14. Januar 2010 20:28 von Rainer Hilmer

Schönes Tutorial. Das kommt in mein Archiv. NInject fehlt noch in meiner Sammlung. Wie ist deine Meinung bezgl. anderer IOC Frameworks wie Unity, StructureMap und autofac? Welches gefällt dir am besten und warum?

# re: Dependency Injection mit Ninject

Donnerstag, 14. Januar 2010 20:45 von ThorstenHans

Hi Rainer,

Danke für das gute Feedback. Ich habe selbst nur Spring.NET, Ninject und WindsorCastle verwendet. Unity wollt ich mir aber in naher Zukunft mal anschauen weil dass beim neuen SharePoint 2010 verwendet wird.

Spring.NET ist halt echt genial. Was ich an Ninject mag, ist der alternative Konfigurationsansatz wie er zum Beispiel auch bei Fluent NHibernate. Zudem ist Ninject echt extrem klein und performant.

# Twitter Trackbacks for Dependency Injection mit Ninject - .NET rocks [dotnet-forum.de] on Topsy.com

Ping Antwort von  Twitter Trackbacks for                 Dependency Injection mit Ninject - .NET rocks         [dotnet-forum.de]        on Topsy.com

# Tutorial Dependency Injection mit Ninject

Freitag, 15. Januar 2010 08:16 von dotnet-kicks.de

Sie wurden gekickt (eine gute Sache) - Trackback von dotnet-kicks.de

# re: Dependency Injection mit Ninject

Montag, 25. Januar 2010 16:32 von Andre Kraemer

Sehr schönes Tutorial. Wir nutzen bei uns übrigens meistens StructureMap. Es ist ähnlich schlank wie Ninject. Der Vorteil den wir sahen, war die Autowirering Funktion. Sprich ich muss für Constructor-Injection nichts annotieren. Standardmässig nimmt sich StructureMap den "größten" Konstruktor. So vermeiden wir eine Abhängigkeit zu einem konkreten DI Container.

# Contextual Binding mit Ninject

Montag, 25. Januar 2010 22:00 von .NET rocks

Im einem meiner letzten Beitrag habe ich Ninject vorgestellt und eine grobe Einweisung gegeben, wie man

# Dependency Injection mit Ninject - .NET rocks

Mittwoch, 21. April 2010 12:09 von Dependency Injection mit Ninject - .NET rocks

Ping Antwort von  Dependency Injection mit Ninject - .NET rocks

Kommentar abgeben

(verpflichtend) 
(verpflichtend) 
(optional)
(verpflichtend)