.NET Dependency Injection (DI) mit Autofac

Dependency Injection in VB und C# .NET mit Autofac
Dependency Injection in VB und C# .NET mit Autofac

Sauberere Programmierung durch Dependency Injection (DI)

Du möchtest Deinen VB.NET, bzw. C# Anwendungen einen „Level Up“ durch Dependency Injection (kurz DI) verpassen? Oder möchtest Du einfach mehr über „das Thema mit den Abhängigkeiten“ lernen? Anders gefragt: Baust Du Dir auch jedes Mal einen neuen Hammer, nur weil Du einen weiteren Nagel in die Wand hämmerst – ne, oder!?

Aus diesem und ähnlichen Gründen macht es auch in der Programmierung keinen Sinn. Welche das genau sind und wie wir unsere .NET Anwendungen mit DI verbessern können, schauen wir uns im heutigen Beitrag an 🤓! Stell‘ Dir einmal vor, Du gehst zum Zahnarzt und Du müsstest vorher selbst Dein Auto bauen. Beim Zahnarzt angekommen, müsste Dieser erstmal alle Geräte bauen und so setzt sich die Kette fort.. Bitte nicht!

Kurzanleitungen

Hast Du es eilig? Kenne ich.. Bekomme einen Überblick ohne Blabla, indem Du Dir dieser Kurzanleitungen anschaust. Wenn Du was nicht verstehst, oder mehr Infos benötigt, kannst Du zu den passenden Sektionen springen. verwende hierzu z. B. das Inhaltsverzeichnis. Lade Dir auch gerne den zu Deinem Projekt passenden Beispielcode herunter, Diesen findest Du unter „Downloads„.

Für VB.NET-Projekte

  1. Installiere Autofac via NuGet Paket Manager
  2. Erstelle die DebugLogger Klasse als Datei im Wurzelverzeichnis
  3. Deaktiviere das Anwendungsframework in den Projekteinstellungen
  4. Erstelle einen manuellen Start-Prozess
  5. Falls 4 nicht funktioniert, passe das in der Projektdatei an
  6. Instanziiere einen ContainerBuilder und registriere die Abhängigkeiten
  7. Lass‘ den ContainerBuilder den Container erstellen und merke Dir Diesen in einer statischen Eigenschaft z. B. in der Program.vb
  8. Lasse die Form vom DI zusammenbauen und zeige Sie an

Für C#-Projekte

  1. Installiere Autofac via NuGet Paket Manager
  2. Erstelle die DebugLogger Klasse als Datei im Wurzelverzeichnis
  3. Instanziiere einen ContainerBuilder und registriere die Abhängigkeiten
  4. Lass‘ den ContainerBuilder den Container erstellen und merke Dir Diesen in einer statischen Eigenschaft z. B. in der Program.vb
  5. Lasse die Form vom DI zusammenbauen und zeige Sie an

Was ist Dependency Injection?

Nunja, bevor wir gleich überhaupt irgendwie mit dem „Wie“ starten können, sollten wir uns vorerst um ein grundsätzlich Verständnis über DI kümmern. Bitte bedenke, dass ich diesen Beitrag hier nicht sprengen möchte! Daher werde ich das Thema „Was ist Dependency Injection“, bzw. den Fokus auf der sachlichen DI-Thematik zu einem späteren Zeitpunkt, separat und in mehr Detail bearbeiten.

Grundsätzlich könnte man die zu Deutsch „Injektion von Abhängigkeiten“ wie folgt, ganz einfach und in ca. 2-3 Sätzen beschreiben:

  1. Dependency Injection ist eine Planungs-/Vorgehensweise bei der Softwareentwicklung.
  2. Dabei achtet man darauf, dass Objekte, weitere Objekte (Abhängigkeiten) bekommen, Welche Sie zur Erledigung ihrer Arbeiten benötigen.
  3. Objekte erstellen also nicht selbst ihre notwendigen Werkzeuge / Abhängigkeiten.

Hierbei kann man ganz leicht viele Beispiele finden:

  1. Der Programmierer mit seinem PC und Co.
  2. Die Erzieherin im Kindergarten
  3. Der Holzfäller mit seiner Axt
  4. Die Friseurin mit Schere und Co.
  5. oder auch der Taxifahrer mit seinem Fahrzeug.

Wie Du Dir schon vorstellen kannst, wäre es nicht wirklich effizient / toll, wenn jede dieser Personen ihre Werkzeuge selbst bauen müsste – ohoh.. Zum Schluss dieses Abschnitts noch ein Hinweis: Streng genommen geht DI auch noch mit einem anderen Prinzip namens „Inversion of Control“ kurz „IoC“ einher. Darüber würde ich dann aber in einem anderen, zukünftigen Beitrag philosophieren.

Warum ist DI gerade in .NET wichtig?

Auch wenn folgendes sicherlich ein gewagter Kommentar ist, werde ich Ihn trotzdem aussprechen. Die Erfahrungen aus den vielen Jahren meiner Entwicklerkarriere zeigen leider, dass besonders VB.NET-Entwickler gegen solche Dinge wie DI „verstoßen“, oder es für Viele eben ein Fremdwort ist. Klar, es ist letztendlich – wie immer – nur eine Art „Hilfs-Regelwerk“, aber man sieht schon allein an diesen Dingen häufig die tatsächliche Erfahrung eines Entwicklers. Ich behaupte: Jeder professionelle Entwickler sollte wissen, was DI ist, wo es konkret unterstützt und wie man es einigermaßen umsetzt.

Häufig höre ich von Menschen aus dem .NET-Umfeld: „Ich programmiere schon 30 Jahre“.. Das freut mich als .NET-Entwickler-Urgestein natürlich, allerdings bringt einem das gar nichts, wenn man selbst nach 30 Jahren immer noch nicht mit DI in Berührung gekommen ist. Ob man das jetzt für ein jeweiliges Projekt nutzt oder nicht, ist immer wieder eine neue Entscheidung. Häufig höre ich dies besonders aus dem VB.NET-Bereich, sprich meinen eigenen Wurzeln.

Bitte verstehe mich an dieser Stelle nicht falsch, ich möchte niemanden geißeln o. Ä., letztendlich ist es ja auch nie für Weiterbildung zu spät. Besonders in der IT gilt ja sowieso: „Bilde Dich weiter, oder Du bleibst auf der Strecke“ und final muss man selbst als Unternehmer oder Angestellter entscheiden, Welche Werkzeuge angesichts der jeweiligen Situation (Budget, Auflagen des Teams, etc.) zum gewünschten Endergebnis führen.

Los geht’s – Dependency Injection Helfer installieren

Nachdem Du nun einen kurzen Crashkurs in die Welt der Dependency Injection an sich erhalten hast, widmen wir uns nun der Programmierung – ich weiß, endlich.. Wie immer gilt: „Erfinde das Fahrrad nicht neu, außer es bringt Dir im Vergleich mehr Vorteile, als es Dich Zeit kostet“. Deshalb werden wir auch hier auf vorhandene und vor Allem etablierte Helferlein zurückgreifen.

Hierbei spreche ich von dem NuGet-Paket namens „Autofac“, Welches mit (aktuell) 201 Millionen Downloads eine ordentliche Hausnummer darstellt. Dementsprechend kann man sich natürlich auch dessen Resonanz in der Entwicklerszene vorstellen. Gehe also bitte für den Anfang hin und installiere dieses Paket aus dem NuGet Paket Manager.

Autofac NuGet Paket Bibliothek
Autofac NuGet Paket Bibliothek

Klicke dazu z. B. mit Rechtsklick auf Dein Projekt im Projektmappenexplorer (oben rechts – nicht auf die Projektmappe selbst!) und wähle „NuGet-Pakete verwalten“ aus. Danach suchst Du wie oben im Bild gezeigt nach „autofac“ und solltest den dementsprechenden Eintrag finden.

Wähle das Paket aus und bestätige die Installation für Dein Projekt auf der rechten Seite des aktuellen Fensters. Nach einem kurzen Moment sollte die Installation abgeschlossen und durch einen kleinen Haken auf dem Icon (links) bestätigt sein. Manchmal hängt Visual Studio jedoch, bzw. ist ein wenig buggy, öffne den NuGet Paketmanager dann einfach nochmal.

Injizierbares Werkzeug erstellen

Um einem Objekt ein anderes, weiteres Objekt zur Verfügung zu stellen (es zu injizieren), benötigen wir erst einmal das Erste. Objekt A wird in unserem Beispiel die obligatorische „Form1“ sein, denn Diese haben wir bereits beim Start eines neuen Projekts. Auch in WPF-Anwendungen können selbstverständlich ähnlich verfahren. Ich denke jedoch, dass jeder .NET-Entwickler die guten alten Windows Forms kennt, daher fange ich der Einfachheit halber damit an.

Nun erstellen wir das Werkzeug, Welches in die Form1 injiziert werden soll. Natürlich kann es anschließend auch in viele andere Ziele injiziert werden.

Erstelle nun eine Klasse namens „DebugLogger“, Welche nur eine kleine Methode namens „Log“ beinhalten wird. Ich möchte den Fokus an dieser Stelle nicht zu sehr auf die Klasse ziehen, daher betrachte Diese hier nicht als mehr, als einen Bauplan für ein Werkzeug.

public class DebugLogger
{

  public DebugLogger()
  {
    
  }

  public void Log(string msg)
  {
    Debug.WriteLine($"{DateTime.Now:HH:mm:ss} {msg}");
  }

}
Public Class DebugLogger

  Sub New()
    
  End Sub

  Public Sub Log(msg As String)
    Debug.WriteLine($"{DateTime.Now:HH:mm:ss} {msg}")
  End Sub

End Class

Ein eigener Start-Prozess

Wenn ich mit heutigem Datum ein VB.NET Projekt erstelle, habe ich das Problem, dass ich – mehr oder weniger – nicht auf den Start-Prozess eingreifen kann. Es ist ohne weitere Kenntnisse nicht wirklich erkenntlich, wo das Programm eigentlich startet. Bei C# ist das schon wesentlich einfacher, bzw. auch schon für Anfänger, durch die separate „Program.cs“-Datei ersichtlicher. Wenn Du also ein C#-Projekt erstellen solltest, wird es hier einfacher und Du könntest Diese Sektion / Schritte überspringen.

Anwendungsframework deaktivieren

Begib Dich hierzu zuerst einmal in die Einstellungen Deines VB.NET-Projektes und deaktiviere das Anwendungsframework. Das ist eine Checkbox, Welche Du unter dem Reiter „Anwendung“ findest. Nur dann hatte ich bisher die Möglichkeit einen manuellen Start-Prozess zu definieren.

Beachte, dass Du diesen Menüpunkt bei modernen Projekttemplates / Visual Studio Versionen einfach via Suchleiste suchen kannst!

VB.NET Anwendungsframework in Windows Forms App deaktivieren
VB.NET Anwendungsframework in Windows Forms App deaktivieren

Manuellen Start-Prozess definieren

Bereiten wir also nun das VB.NET Windows Forms Projekt vor, damit wir hier auf einem „gleichen Stand“, wie bei einem neuen C#-Projekt sind. Erstelle analog zu einem C#-Projekt einfach eine „Program.vb“-Datei innerhalb des Wurzelverzeichnisses Deines Projekts.

Des Weiteren benötigt jede gängige Anwendung eine „Main“-Methode. Diese erstellen wir nun in einem typischen Sinne. Um unsere Anwendung dann tatsächlich starten / ausführen zu lassen, benötigen wir die „Application.Run„-Methode. Für gewöhnlich erstellt man einfach eine Instanz der jeweiligen zu anzeigenden Form und übergibt Dieser der erwähnten Run-Methode.

public class Program
{

  static void Main(string[] args)
  {
    Application.EnableVisualStyles();

    // App wird geschlossen, wenn Form1 geschlossen wird
    var frm = new Form1();
    Application.Run(frm);

    // erlaubt mehrere Formulare
    // du musst dich manuell um das Schließen der App kümmern!
    // var frm = new Form1();
    // frm.Show();
    // Application.Run();
  }

}
Public Class Program

    Public Shared Sub Main(args As String())
      Application.EnableVisualStyles()

      ' App wird geschlossen, wenn Form1 geschlossen wird
      Dim frm = New Form1()
      Application.Run(frm)

      ' erlaubt mehrere Formulare
      ' du musst dich manuell um das Schließen der App kümmern!
      ' Dim frm = New Form1()
      ' frm.Show()
      ' Application.Run()
    End Sub

End Class

Wenn Du nicht möchtest, dass sich das Programm schließt, wenn Du diese Form schließt, dann muss der Aufruf anders aussehen. Schaue Dir dazu das untere Kommentar und den dazugehörigen Code an. Schaue auch gerne in meinem Beitrag über Windows Formulare und Dialoge vorbei.

Wähle nun final die „neue“ Main-Methode in den Projekteinstellungen aus, damit Dein Programm auch tatsächlich dort loslegt. Begib Dich dazu erneut zu „Projekt->Eigenschaften->Anwendung“ und wähle bei „Startobjekt“ die jeweilige Main-Methode. Beachte, dass dies alles bei C# nicht notwendig ist, da Du eine fertige Konfiguration in der „Program.cs“ finden wirst.

VB.NET Anwendung Startobjekt manuell festlegen
VB.NET Anwendung Startobjekt manuell festlegen

Wichtiger Hinweis

Falls Du Deine Main-Methode nicht in der obigen Combobox finden kannst, habe ich eine Lösung für Dich. Beim meinem Visual Studio 2022 und dem Projekttemplate „Windows Forms Anwendung“ für .NET 6 musst ich auch kurz fummeln – aber gut.. Das macht man dann einmal und gut ist – danach weiß man es ja schließlich. Sollte leider auch nur VB.NET Projekte betreffen..

Ich gehe nun im ersten Schritt davon aus, dass Du die Main-Methode korrekt angelegt hast:

  • In einer Klasse
  • Public
  • Shared
  • Mit der typischen „args As String()“ Signatur

Falls Sie trotzdem nicht auftaucht, kannst Du das Problem manuell fixen. Mache rechts im Projektmappenexplorer einen Rechtsklick auf Dein Projekt und wähle „Projektdatei bearbeiten“ aus. Dort kannst Du Dein Startobjekt nun mit einem passenden Tag angeben.

Schreibe dazu folgendes Tag hinein:

<StartupObject>{HierDerNamespaceDeinesProgramms}.{HierDasStartobjekt}</StartupObject>

Sieht dann final wie folgt aus – bitte nur das rote Rechteck anpassen, da Du sonst Dein Projekt zerschießen könntest:

VB.NET Anwendung Startobjekt manuell in Projektdatei festlegen
VB.NET Anwendung Startobjekt manuell in Projektdatei festlegen

Autofac kommt in’s Spiel

Nachdem wir im vorherigen Schritt alle Vorbereitungen getroffen haben, müsste Dein Projekt jetzt den gleichen Startpunkt wie im Code hier drüber haben. Bei C# musstest Du dafür nicht wirklich etwas tun, denn dort gibt es die „Program.cs“-Datei, sprich wo alles gestartet wird, von vornherein. Bei VB.NET muss man allerdings ein wenig Konfigurationsarbeit leisten, schaue dazu in den passenden Abschnitt.

Nun können wir Autofac benutzen, um tatsächliche Abhängigkeiten – wie unseren „DebugLogger“ – zu registrieren. Dies geschieht in einer von Autofac bereitgestellten Klasse namens „ContainerBuilder“, also natürlich in einer Instanz davon. Dieser Builder erstellt uns anschließend einen fertigen, sogenannten „Container“, wenn wir die „Build“-Methode aufrufen.

Dieser beinhaltet alle notwendigen Abhängigkeiten, bzw. auch die Pläne, wie Diese erstellt werden können. Das hängt letztendlich von der Registrierungs-Art innerhalb des sogenannten DI-Containers ab. Hier gehe ich aber nicht zu tief in diesem Beitrag. Den erstellten Container müssen wir dann zwischenspeichern, damit wir später Zugriff darauf haben. In diesem Fall ist meines Erachtens nach auch ein öffentliches, statisches Feld / Eigenschaft passend.

Wir passen den Inhalt der Program-Datei also wie gleich folgend an. Beachte auch, dass nicht nur der „DebugLogger“, sondern auch die „Form1“ selbst registriert wird.

// wo die Imports halt hinkommen..
using Autofac;

// außerhalb der Main-Methode, aber innerhalb der Program-Datei
public static IContainer Container { get; set; }

// Inhalt der Main-Methode
var builder = new ContainerBuilder();
builder.RegisterType<Form1>();
builder.RegisterType<DebugLogger>();
Container = builder.Build();
' wo die Imports halt hinkommen..
Imports Autofac

' außerhalb der Main-Methode, aber innerhalb der Program-Datei
Public Shared Property Container As IContainer

' Inhalt der Main-Methode
Dim builder = new ContainerBuilder()
builder.RegisterType(Of Form1)()
builder.RegisterType(Of DebugLogger)()
Container = builder.Build()

Eine (korrekte) Abhängigkeit erschaffen

Im vorletzten Schritt müssen wir unserem Programm nun beibringen, dass die Form1 tatsächlich von unserem „DebugLogger“ abhängig ist. Anders gesprochen: ..dass unsere Form den DebugLogger benötigt, um Ihre Arbeit / Funktionlität zu erledigen.

Der wichtigste Unterschied ist nun nach all Dem, dass wir den fertigen, gleichen DebugLogger in die Form injizieren werden. Okay, um genau zu sein, tun wir dies nicht selbst, es geschieht stattdessen über den DI-Container, Welchen wir vorher durch den „ContainerBuilder“ erstellt haben.

Nun bringen wir unserer Form bei: „Hey, ich benötige den DebugLogger zur Erledigung meiner Arbeit“. Dies geschieht in den meisten Fällen über die sogenannte Konstruktor-Injektion, naja, eben weil Sie im Konstruktor stattfindet:

public class Form1
{

  // Deklaration der Abhängigkeit
  DebugLogger _logger;

  public Form1(DebugLogger logger)
  {
    InitializeComponent();
    // der logger kommt in die Instanz "hinein"
    // von nun an, kann Dieser überall wo notwendig
    // verwendet werden
    _logger = logger;
    Load += Form1_Load;
  }

  private void Form1_Load(object sender, EventArgs e)
  {
   // die erste Verwendung unseres Loggers!!
    _logger.Log("Yay, Form1_Load wurde aufgerufen!");
  }

}
Public Class Form1

  ' Deklaration der Abhängigkeit
  Private _logger As DebugLogger

  Sub New(logger As DebugLogger)
    InitializeComponent()
    ' der logger kommt in die Instanz "hinein"
    ' von nun an, kann Dieser überall wo notwendig
    ' verwendet werden
    _logger = logger
  End Sub

  Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    ' die erste Verwendung unseres Loggers!!
    _logger.Log("Yay, Form1_Load wurde aufgerufen!")
  End Sub

End Class

Die Form richtig „auflösen“

Wichtig: Hierbei geht es nicht um eine Art Brausetablette! Auflösen bedeutet im Kontext der Dependency Injection, dass man die jeweilig definierten Abhängigkeiten aus dem Container abruft. Das Ziel-Objekt kann dann anschließend damit bestückt werden. Dazu bietet uns die Container-Instanz der Autofac-Bibliothek eine Methode namens „Resolve“ an.

Hier kannst Du nun den finalen Code mit ein wenig Refactoring sehen, beachte noch den Form-Code aus dem vorherigen Abschnitt:

using Autofac;

public class Program
{

  public static IContainer Container { get; set; }

  static void Main(string[] args)
  {
    PrepareDI();
    RunApp();
  }

  private static void PrepareDI()
  {
    var builder = new ContainerBuilder();
    builder.RegisterType<DebugLogger>();
    builder.RegisterType<Form1>();
    Container = builder.Build();
  }

  private static void RunApp()
  { 
    Application.EnableVisualStyles();
    var frm = Container.Resolve<Form1>();
    frm.Show();
    Application.Run();
  }

}
Imports Autofac

Public Class Program

    Public Shared Property Container As IContainer

    Private Shared Sub Main(args As String())
        PrepareDI()
        RunApp()
    End Sub

    Private Shared Sub PrepareDI()
        Dim builder = New ContainerBuilder()
        builder.RegisterType(Of DebugLogger)()
        builder.RegisterType(Of Form1)()
        Container = builder.Build()
    End Sub

    Private Shared Sub RunApp()
        Application.EnableVisualStyles()
        Dim frm = Container.Resolve(Of Form1)()
        frm.Show()
        Application.Run()
    End Sub

End Class

Downloads

Wie (fast) immer biete ich Dir hier den kompletten Code zum Beitrag als Download an, so kannst Du alles schnell nachbauen und testen. Ziehe Dir einfach das Projekt Deiner Wahl, ob nun VB.NET oder C#!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert