Udostępnij za pośrednictwem


Omówienie podstaw iniekcji zależności na platformie .NET

W tym artykule utworzysz aplikację konsolową platformy .NET, która ręcznie utworzy odpowiedni ServiceProviderelement ServiceCollection i . Dowiesz się, jak zarejestrować usługi i rozwiązać je przy użyciu wstrzykiwania zależności (DI). W tym artykule użyto pakietu NuGet Microsoft.Extensions.DependencyInjection , aby zademonstrować podstawy di na platformie .NET.

Uwaga

Ten artykuł nie korzysta z funkcji hosta ogólnego. Aby uzyskać bardziej kompleksowy przewodnik, zobacz Use dependency injection in .NET (Używanie wstrzykiwania zależności na platformie .NET).

Rozpocznij

Aby rozpocząć, utwórz nową aplikację konsolową platformy .NET o nazwie DI.Basics. Niektóre z najbardziej typowych podejść do tworzenia projektu konsoli znajdują się na poniższej liście:

Należy dodać odwołanie do pakietu do pliku projektu Microsoft.Extensions.DependencyInjection . Niezależnie od podejścia upewnij się, że projekt przypomina następujący kod XML pliku DI.Basics.csproj :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
  </ItemGroup>

</Project>

Podstawy wstrzykiwania zależności

Wstrzykiwanie zależności to wzorzec projektowy, który umożliwia usuwanie trwale zakodowanych zależności i zwiększenie możliwości konserwacji i testowania aplikacji. Di jest techniką osiągnięcia inwersji kontroli (IoC) między klasami i ich zależnościami.

Abstrakcje di na platformie .NET są definiowane w pakiecie NuGet Microsoft.Extensions.DependencyInjection.Abstractions :

Na platformie .NET di jest zarządzane przez dodawanie usług i konfigurowanie ich w programie IServiceCollection. Po zarejestrowaniu IServiceProvider BuildServiceProvider usług wystąpienie jest tworzone przez wywołanie metody . Działa IServiceProvider jako kontener wszystkich zarejestrowanych usług i służy do rozwiązywania problemów z usługami.

Tworzenie example usług

Nie wszystkie usługi są tworzone w równym stopniu. Niektóre usługi wymagają nowego wystąpienia za każdym razem, gdy kontener usługi je pobiera (transient), podczas gdy inne powinny być współużytkowane przez żądania (scoped) lub przez cały okres istnienia aplikacji (singleton). Aby uzyskać więcej informacji na temat okresów istnienia usług, zobacz Okresy istnienia usługi.

Podobnie niektóre usługi uwidaczniają tylko konkretny typ, podczas gdy inne są wyrażane jako kontrakt między interfejsem a typem implementacji. Utworzysz kilka odmian usług, aby zademonstrować te pojęcia.

Utwórz nowy plik języka C# o nazwie IConsole.cs i dodaj następujący kod:

public interface IConsole
{
    void WriteLine(string message);
}

Ten plik definiuje IConsole interfejs, który uwidacznia jedną metodę . WriteLine Następnie utwórz nowy plik C# o nazwie DefaultConsole.cs i dodaj następujący kod:

internal sealed class DefaultConsole : IConsole
{
    public bool IsEnabled { get; set; } = true;

    void IConsole.WriteLine(string message)
    {
        if (IsEnabled is false)
        {
            return;
        }

        Console.WriteLine(message);
    }
}

Powyższy kod reprezentuje domyślną implementację interfejsu IConsole . Metoda WriteLine warunkowo zapisuje w konsoli na IsEnabled podstawie właściwości .

Napiwek

Nazewnictwo implementacji jest wyborem, w jaki zespół deweloperów powinien się zgodzić. Prefiks Default to wspólna konwencja wskazująca domyślną implementację interfejsu, ale nie jest wymagana.

Następnie utwórz plik IGreetingService.cs i dodaj następujący kod w języku C#:

public interface IGreetingService
{
    string Greet(string name);
}

Następnie dodaj nowy plik C# o nazwie DefaultGreetingService.cs i dodaj następujący kod:

internal sealed class DefaultGreetingService(
    IConsole console) : IGreetingService
{
    public string Greet(string name)
    {
        var greeting = $"Hello, {name}!";

        console.WriteLine(greeting);

        return greeting;
    }
}

Powyższy kod reprezentuje domyślną implementację interfejsu IGreetingService . Implementacja usługi wymaga IConsole jako podstawowego parametru konstruktora. Metoda Greet:

  • Tworzy dany greeting element name.
  • Wywołuje metodę WriteLine w wystąpieniu IConsole .
  • Zwraca element greeting do elementu wywołującego.

Ostatnią usługą do utworzenia jest plik FarewellService.cs , przed kontynuowaniem dodaj następujący kod języka C#:

public class FarewellService(IConsole console)
{
    public string SayGoodbye(string name)
    {
        var farewell = $"Goodbye, {name}!";

        console.WriteLine(farewell);

        return farewell;
    }
}

Obiekt FarewellService reprezentuje konkretny typ, a nie interfejs. Należy je zadeklarować public , aby była dostępna dla konsumentów. W przeciwieństwie do innych typów implementacji usług, które zostały zadeklarowane jako internal i sealed, ten kod pokazuje, że nie wszystkie usługi muszą być interfejsami. Pokazuje również, że implementacje usług mogą sealed uniemożliwić dziedziczenie i internal ograniczyć dostęp do zestawu.

Program Aktualizowanie klasy

Otwórz plik Program.cs i zastąp istniejący kod następującym kodem c#:

using Microsoft.Extensions.DependencyInjection;

// 1. Create the service collection.
var services = new ServiceCollection();

// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    });
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();

// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();

// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();

// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");

Powyższy zaktualizowany kod przedstawia instrukcje:

  • Utwórz nowe ServiceCollection wystąpienie.
  • Rejestrowanie i konfigurowanie usług w programie ServiceCollection:
    • Użycie IConsole przeciążenia fabryki implementacji zwraca DefaultConsole typ z zestawem IsEnabled na wartość "true".
    • Element IGreetingService jest dodawany z odpowiednim typem implementacji DefaultGreetingService typu.
    • Element FarewellService jest dodawany jako typ betonowy.
  • Skompiluj element ServiceProvider z pliku ServiceCollection.
  • Rozwiąż problemy z usługami IGreetingService i FarewellService .
  • Skorzystaj z rozwiązanych usług, aby powitać i pożegnać się z osobą o imieniu David.

Jeśli zaktualizujesz IsEnabled właściwość elementu DefaultConsole do false, Greet metody i SayGoodbye pomijają zapisywanie w wynikowych komunikatach w konsoli. Taka zmiana pomaga pokazać, że IConsole usługa jest wprowadzana do IGreetingService usług i FarewellService jako zależność , która wpływa na zachowanie aplikacji.

Wszystkie te usługi są rejestrowane jako singletony, chociaż w przypadku tego przykładu działają identycznie, jeśli zostały zarejestrowane jako transient usługi lub scoped .

Ważne

W tym spisanym exampleokresie istnienia usługi nie mają znaczenia, ale w rzeczywistej aplikacji należy dokładnie rozważyć okres istnienia każdej usługi.

Uruchamianie przykładowej aplikacji

Aby uruchomić przykładową aplikację, naciśnij F5 w programie Visual Studio, Visual Studio Code lub uruchom dotnet run polecenie w terminalu. Po zakończeniu pracy aplikacji powinny zostać wyświetlone następujące dane wyjściowe:

Hello, David!
Goodbye, David!

Deskryptory usług

Najczęściej używane interfejsy API do dodawania usług do klasy ServiceCollection to metody rozszerzenia ogólnego o nazwie okresu istnienia, takie jak:

  • AddSingleton<TService>
  • AddTransient<TService>
  • AddScoped<TService>

Te metody to wygodne metody, które tworzą ServiceDescriptor wystąpienie i dodają je do klasy ServiceCollection. Jest ServiceDescriptor to prosta klasa, która opisuje usługę z jej typem usługi, typem implementacji i okresem istnienia. Może również rozdzielić fabryki implementacji i wystąpienia.

Dla każdej usługi zarejestrowanej w programie ServiceCollectionmożna zamiast tego wywołać Add metodę bezpośrednio z wystąpieniem ServiceDescriptor . Rozważ następujące przykłady:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IConsole),
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    },
    lifetime: ServiceLifetime.Singleton));

Powyższy kod jest odpowiednikiem sposobu zarejestrowania IConsole usługi w obiekcie ServiceCollection. Metoda Add służy do dodawania wystąpienia opisującego usługę ServiceDescriptor IConsole . Metoda statyczna ServiceDescriptor.Describe deleguje do różnych ServiceDescriptor konstruktorów. Rozważ odpowiedni kod dla IGreetingService usługi:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IGreetingService),
    implementationType: typeof(DefaultGreetingService),
    lifetime: ServiceLifetime.Singleton));

Powyższy kod opisuje usługę IGreetingService z jej typem usługi, typem implementacji i okresem istnienia. Na koniec rozważ odpowiedni kod dla FarewellService usługi:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(FarewellService),
    implementationType: typeof(FarewellService),
    lifetime: ServiceLifetime.Singleton));

Powyższy kod opisuje konkretny FarewellService typ zarówno usługi, jak i typów implementacji. Usługa jest zarejestrowana singleton jako usługa.

Zobacz też