Dela via


Förstå grunderna för beroendeinmatning i .NET

I den här artikeln skapar du en .NET-konsolapp som manuellt skapar en ServiceCollection och motsvarande ServiceProvider. Du lär dig hur du registrerar tjänster och löser dem med hjälp av beroendeinmatning (DI). Den här artikeln använder NuGet-paketet Microsoft.Extensions.DependencyInjection för att demonstrera grunderna i DI i .NET.

Kommentar

Den här artikeln drar inte nytta av funktionerna för generisk värd. En mer omfattande guide finns i Använda beroendeinmatning i .NET.

Kom igång

Kom igång genom att skapa ett nytt .NET-konsolprogram med namnet DI.Basics. Några av de vanligaste metoderna för att skapa ett konsolprojekt refereras i följande lista:

Du måste lägga till paketreferensen till Microsoft.Extensions.DependencyInjection i projektfilen. Oavsett metod bör du se till att projektet liknar följande XML för FILEN 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>

Grunderna för beroendeinmatning

Beroendeinmatning är ett designmönster som gör att du kan ta bort hårdkodade beroenden och göra programmet mer underhållsbart och testbart. DI är en teknik för att uppnå inversion av kontroll (IoC) mellan klasser och deras beroenden.

Abstraktionerna för DI i .NET definieras i NuGet-paketet Microsoft.Extensions.DependencyInjection.Abstractions :

I .NET hanteras DI genom att lägga till tjänster och konfigurera dem i en IServiceCollection. När tjänsterna har registrerats skapas en IServiceProvider instans genom att metoden anropas BuildServiceProvider . Fungerar IServiceProvider som en container för alla registrerade tjänster och används för att lösa tjänster.

Skapa example tjänster

Alla tjänster skapas inte lika. Vissa tjänster kräver en ny instans varje gång som tjänstcontainern hämtar dem (transient), medan andra ska delas mellan begäranden (scoped) eller under hela appens livslängd (singleton). Mer information om tjänstlivslängder finns i Tjänstlivslängder.

På samma sätt exponerar vissa tjänster bara en konkret typ, medan andra uttrycks som ett kontrakt mellan ett gränssnitt och en implementeringstyp. Du skapar flera varianter av tjänster för att demonstrera dessa begrepp.

Skapa en ny C#-fil med namnet IConsole.cs och lägg till följande kod:

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

Den här filen definierar ett IConsole gränssnitt som exponerar en enda metod, WriteLine. Skapa sedan en ny C#-fil med namnet DefaultConsole.cs och lägg till följande 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);
    }
}

Föregående kod representerar standardimplementeringen av IConsole gränssnittet. Metoden WriteLine skriver villkorligt till konsolen baserat på IsEnabled egenskapen .

Dricks

Namngivning av en implementering är ett val som utvecklingsteamet bör komma överens om. Prefixet Default är en vanlig konvention som anger en standardimplementering av ett gränssnitt, men det krävs inte .

Skapa sedan en IGreetingService.cs fil och lägg till följande C#-kod:

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

Lägg sedan till en ny C#-fil med namnet DefaultGreetingService.cs och lägg till följande kod:

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

        console.WriteLine(greeting);

        return greeting;
    }
}

Föregående kod representerar standardimplementeringen av IGreetingService gränssnittet. Tjänstimplementeringen kräver en IConsole som primär konstruktorparameter. Metoden Greet :

  • Skapar en greeting angiven name.
  • WriteLine Anropar metoden på instansenIConsole.
  • greeting Returnerar till anroparen.

Den sista tjänsten som ska skapas är FarewellService.cs-filen, lägg till följande C#-kod innan du fortsätter:

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

        console.WriteLine(farewell);

        return farewell;
    }
}

Representerar FarewellService en konkret typ, inte ett gränssnitt. Den bör deklareras så public att den blir tillgänglig för konsumenterna. Till skillnad från andra tjänstimplementeringstyper som deklarerades som internal och sealedvisar den här koden att inte alla tjänster behöver vara gränssnitt. Det visar också att tjänstimplementeringar kan vara sealed att förhindra arv och internal begränsa åtkomsten till sammansättningen.

Program Uppdatera klassen

Öppna filen Program.cs och ersätt den befintliga koden med följande C#-kod:

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");

Föregående uppdaterade kod visar instruktioner:

  • Skapa en ny ServiceCollection instans.
  • Registrera och konfigurera tjänster i ServiceCollection:
    • När IConsole du använder överlagringen av implementeringsfabriken IsEnabled returnerar du en DefaultConsole typ med värdet "true".
    • IGreetingService Läggs till med en motsvarande implementeringstyp av DefaultGreetingService typen.
    • FarewellService Läggs till som en betongtyp.
  • ServiceProvider Skapa från ServiceCollection.
  • Lös tjänsterna IGreetingService och FarewellService .
  • Använd de lösta tjänsterna för att hälsa och säga adjö till en person med namnet David.

Om du uppdaterar IsEnabled egenskapen DefaultConsole för till falseutelämnar Greet metoderna och SayGoodbye skrivningen till de resulterande meddelandena till konsolen. En ändring som den här hjälper till att visa att IConsole tjänsten matas in i IGreetingService tjänsterna och FarewellService som ett beroende som påverkar apparnas beteende.

Alla dessa tjänster är registrerade som singletons, men för det här exemplet fungerar de identiskt om de har registrerats som transient eller scoped tjänster.

Viktigt!

I den här contrived examplespelar tjänstlivslängden ingen roll, men i ett verkligt program bör du noga överväga livslängden för varje tjänst.

Kör exempelappen

Om du vill köra exempelappen trycker du antingen på F5 i Visual Studio, Visual Studio Code eller kör dotnet run kommandot i terminalen. När appen är klar bör du se följande utdata:

Hello, David!
Goodbye, David!

Tjänstbeskrivningar

De vanligaste API:erna för att lägga till tjänster i ServiceCollection är livslängdsnamnsnamns generiska tilläggsmetoder, till exempel:

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

Dessa metoder är bekvämlighetsmetoder som skapar en ServiceDescriptor instans och lägger till den i ServiceCollection. ServiceDescriptor är en enkel klass som beskriver en tjänst med dess tjänsttyp, implementeringstyp och livslängd. Det kan också desribe implementeringsfabriker och instanser.

För var och en av de tjänster som du registrerade i kan du i ServiceCollectionstället anropa Add metoden med en ServiceDescriptor instans direkt. Föreställ dig följande exempel:

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

Föregående kod motsvarar hur IConsole tjänsten registrerades i ServiceCollection. Metoden Add används för att lägga till en ServiceDescriptor instans som beskriver IConsole tjänsten. Den statiska metoden ServiceDescriptor.Describe delegerar till olika ServiceDescriptor konstruktorer. Överväg motsvarande kod för IGreetingService tjänsten:

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

Föregående kod beskriver tjänsten IGreetingService med dess tjänsttyp, implementeringstyp och livslängd. Tänk slutligen på motsvarande kod för FarewellService tjänsten:

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

Föregående kod beskriver den konkreta FarewellService typen som både tjänst- och implementeringstyper. Tjänsten är registrerad som en singleton tjänst.

Se även