C# Nachträgliches Hinzufügen bei ServiceCollection

Ghost_Rider_R

Lieutenant
Registriert
Nov. 2009
Beiträge
759
Hallo zusammen,

ich habe eine ServiceCollection, bei der ich nach dem Aufruf von BuildServiceProvider() nochmals eine Singleton-Instanz hinzufügen müsste, da die nachträglich hinzugefügte Instanz ein Objekt des ServiceProviders benötigt.

Kann man das irgendwie machen oder ist der Ansatz generell nicht im Sinne des Erfinders?

Das Problem ist, dass die nachträglich hinzugefügte Instanz aus einer Methode kommt und die benötigt selbst eine Instanz aus dem Service Provider d.h. kann ich diese erst aufrufen, nachdem der Service Provider erstellt wurde.

Vielen Dank für eure Hilfe.

LG Ghost.
 
Das würde ich mittels Factory / Abstract Factory Pattern realisieren. Wenn eine Klasse deine Instanz benötigt, die erst nachträglich erstellt werden kann, dann verwendet sie die Factory. Diese Factory ist in der Lage die Instanz zu erstellen.

C#:
public interface IFoobarFactory
{
    Foobar CreateFoobar();
}

public class FoobarFactory : IFoobarFactory
{
    private readonly IServiceProvider _serviceProvider;

    public FoobarFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    // or any other instance creation
    public Foobar CreateFoobar() => _serviceProvider.GetService<Foobar>();
}

Um Foobar zu erhalten nutzen alle Klassen die IFoobarFactory. Die Klasse Foobar kann auch innerhalb der Factory mittels Lazy vorgehalten werden, um einen Singleton bereitzustellen.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
Das kann man auch ohne extra Factory-Interface direkt beim Registrieren in der ServiceCollection machen (wenn es nicht allzu komplex ist).

C#:
services.AddSingleton(sp => sp.GetRequiredService<RequiredInstance>().CreateTheOtherClass());

Bin nur am Handy, aber so sollte es in etwa passen.
 
@marcOcram
So ähnlich hatte ich es davor aber soweit ich das gesehen habe wird direkt bei erreichen dieser Codestelle die Methode sp.GetRequiredService... aufgerufen und das dann erzeuge Objekt wird dann vorgehalten. Es wird also vor dem Erstellen des Providers ausgeführt und das war mein Problem.

Die Lösung @efcoyote erscheint mir am naheliegendsten.
 
Dann liegt der Fehler woanders. Damit wird konkret eine Factory registriert. Diese wird erst aufgerufen wenn das Objekt benötigt wird und es kann auch erst aufgerufen werden, wenn der ServiceProvider gebaut wurde. Das würde ja sonst gar nicht funktionieren. Woher soll der ServiceProvider denn kommen wenn er noch nicht gebaut wurde?

Ich kann bei Bedarf gerne ein Fiddle dazu erstellen, aber es muss so funktionieren.
 
Vielleicht verstehe ich dich auch falsch @marcOcram
Mein Ansatz war so:

C#:
using Microsoft.Extensions.DependencyInjection;

public class Test : ITest
{
    public Test() => Console.WriteLine("Ausgeführt");
}

public interface ITest { }

public class Program
{
    public static Test ErstellenTest()
    {
        return new Test();
    }

    static void Main()
    {
        IServiceCollection serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<ITest>(ErstellenTest());
        Console.WriteLine("Fast zu Ende");
        IServiceProvider sp = serviceCollection.BuildServiceProvider();
        Console.WriteLine("Ende");
        Console.ReadLine();
    }
}

Die Ausgabe war dann so:

1681417654644.png
 
Deine Zeile 20 könnte man auch zweizeilig schreiben, dann fällt das Problem sofort auf.

C#:
        ITest test = ErstellenTest();
        serviceCollection.AddSingleton<ITest>(test);

Probier es mal mit dem richtigen Factory-Aufruf.

C#:
using Microsoft.Extensions.DependencyInjection;

public class Test : ITest
{
    public Test() => Console.WriteLine("Ausgeführt");
}

public interface ITest { }

public class Program
{
    public static Test ErstellenTest(IServiceProvider serviceProvider)
    {
        return new Test();
    }

    static void Main()
    {
        IServiceCollection serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<ITest>(ErstellenTest);
        // oder serviceCollection.AddSingleton<ITest>(sp => new Test());
        Console.WriteLine("Fast zu Ende");
        IServiceProvider sp = serviceCollection.BuildServiceProvider();
        Console.WriteLine("Ende");
        // das ist neu um die Factory aufzurufen
        _ = sp.GetRequiredService<ITest>();
        Console.ReadLine();
    }
}

P.S. Bin immer noch am Handy, daher ungetestet.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
So funktioniert es tatsächlich!

C#:
using Microsoft.Extensions.DependencyInjection;


public class Test : ITest
{
    public Test() => Console.WriteLine("Eins");
}

public interface ITest { }

public class Program
{
    public static ITest ErstellenTest() => new Test();

    static void Main()
    {
        IServiceCollection serviceCollection = new ServiceCollection();

        //Ausgabe: Eins, Zwei, Drei
        //serviceCollection.AddSingleton<ITest>(ErstellenTest());

        //Ausgabe: Zwei, Eins, Drei
        //serviceCollection.AddSingleton<ITest>(abc => new Test());

        //Ausgabe: Zwei, Eins, Drei
        //serviceCollection.AddSingleton<ITest>(abc => ErstellenTest());

        Console.WriteLine("Zwei");
        IServiceProvider sp = serviceCollection.BuildServiceProvider();
        ITest test = sp.GetRequiredService<ITest>();
        Console.WriteLine("Drei");
        Console.ReadLine();
    }
}

Vielen Dank für den Tipp!
 
Ich bräuchte bitte nochmals eure Hilfe.
Könnte mir bitte jemand sagen, warum die obere Zeile nicht funktioniert aber die untere?
einen Syntaxfehler gibt es nicht:

C#:
//Geht nicht:
Func<ICheckManager> erstellenCheckManagerFunktion = ErstellenCheckManager;
serviceCollection.AddSingleton(erstellenCheckManagerFunktion);

//Geht:
serviceCollection.AddSingleton((abc) => ErstellenCheckManager());

Ist das nicht in beiden Varianten genau die gleiche Anweisung?
 
Nein, du brauchst in der oberen Definition noch einen Parameter vom Typ IServiceProvider den es bei der unteren gibt (abc).

Demnach also Func<IServiceProvider, ICheckManager>.

Aktuell registrierst du nicht den ICheckManager als Singleton. Deswegen auch kein Syntaxfehler. Schreib es mal explizit als AddSingleton<ICheckManager>, dann kommt der Compilerfehler.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
@marcOcram
Das stimmt was du sagst, ich mache da wohl aktuell noch einen Fehler bei der Func.

C#:
Func<IServiceProvider, IFunktionsListe> erstellenFunktionsListeFunktion = ErstellenFunktionsListe;
serviceCollection.AddSingleton<IFunktionsListe>(erstellenFunktionsListeFunktion);

Der IServiceProvider ist doch ein Parameter für die Methode ErstellenFunktionsListe
und IFunktionsListe beschreibt hier den Rückgabewert der Methode ErstellenFunktionsListe.

Ist das korrekt? in meinem Fall hat die Methode ErstellenFunktionsListe aber keinen Parameter...

Wie müsste das dann aussehen?
Ergänzung ()

Ok, ich habe mir die Frage selbst beantworten können.

So funktioniert es:
C#:
Func<IServiceProvider, IFunktionsListe> erstellenFunktionsListeFunktion = (serviceProvider) => ErstellenFunktionsListe();
serviceCollection.AddSingleton(erstellenFunktionsListeFunktion);

Oder die Kurzform so:
C#:
serviceCollection.AddSingleton((serviceProvider) => ErstellenFunktionsListe());

@marcOcram Besten Dank für deine Hilfe!
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: marcOcram
Das ist soweit richtig. Eine Func ist einfach gesagt nichts anderes als eine Funktion mit 0..n Parametern und einem Rückgabewert. Diese kann auf eine Methode zeigen oder anonym als Lambda definiert werden.

In deinem Fall hast du drei Möglichkeiten:
  1. Die Methode ErstellenFunktionsListe um den Parameter erweitern und diesen ignorieren
  2. Den Ausdruck Func<IServiceProvider, IFunktionsListe> erstellenFunktionsListeFunktion = ErstellenFunktionsListe; als Lambda schreiben: Func<IServiceProvider, IFunktionsListe> erstellenFunktionsListeFunktion = sp => ErstellenFunktionsListe(); und dort den Parameter ignorieren.
  3. Eine Methode schreiben die als Parameter einen IServiceProvider hat und einfach ErstellenFunktionsListe(); aufruft. Das wäre Möglichkeit Zwei als nicht-Lambda.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
Zurück
Oben