Konstruktorinjektion och gränssnitt
Använd konstruktorinjektion med gränssnittsbaserad design för att frikoppla komponenter och möjliggöra enhetstestning.
Konstruktorinjektion och gränssnitt är en gratis lektion i C# Academy på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för C# Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i C# Academy innehåller totalt 4 lektioner.
Grunderna i konstruktorinjektion
Konstruktorinjektion är det vanligaste DI-mönstret. En klass deklarerar sina beroenden som konstruktorparametrar, och containern tillhandahåller dem när instansen skapas.
Det gör beroendena explicit angivna, obligatoriska och synliga.
Definiera gränssnittet
Börja med ett gränssnitt som definierar kontraktet. Då kan olika implementationer (verkliga, mockade eller minnesbaserade) bytas ut utan att den beroende koden ändras.
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Task AddAsync(Product product);
}Implementera gränssnittet
Den konkreta implementationen utför det faktiska arbetet – databasanslutningar, HTTP-anrop och så vidare. Konsumenten behöver aldrig veta vilken implementation den får.
public class SqlProductRepository : IProductRepository
{
private readonly AppDbContext _db;
public SqlProductRepository(AppDbContext db) => _db = db;
public async Task<Product?> GetByIdAsync(int id)
=> await _db.Products.FindAsync(id);
public async Task<IEnumerable<Product>> GetAllAsync()
=> await _db.Products.ToListAsync();
public async Task AddAsync(Product product)
{
_db.Products.Add(product);
await _db.SaveChangesAsync();
}
}Använda via konstruktorn
Tjänstelagret tar emot IProductRepository som en konstruktorparameter. Det känner aldrig till SqlProductRepository – bara gränssnittskontraktet.
public class ProductService
{
private readonly IProductRepository _repo;
private readonly ILogger<ProductService> _logger;
public ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
_repo = repo;
_logger = logger;
}
public async Task<Product?> GetProductAsync(int id)
{
_logger.LogInformation("Fetching product {Id}", id);
return await _repo.GetByIdAsync(id);
}
}Registrera kedjan
Registrera alla tjänster i kedjan. Containern löser automatiskt upp hela beroendegrafen – ProductService → IProductRepository → AppDbContext.
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite("Data Source=app.db"));
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddScoped<ProductService>();Testa med en mock
Eftersom beroenden injiceras via gränssnitt kan enhetstester använda en mock eller falsk implementation utan att röra databasen. Det är en av DI:s största fördelar.
// Using NSubstitute or Moq in a unit test
var mockRepo = Substitute.For<IProductRepository>();
mockRepo.GetByIdAsync(1).Returns(new Product { Id = 1, Name = "Widget" });
var logger = Substitute.For<ILogger<ProductService>>();
var svc = new ProductService(mockRepo, logger);
var result = await svc.GetProductAsync(1);
Assert.Equal("Widget", result?.Name);Flera konstruktorparametrar
En klass kan ha många konstruktorparametrar. Containern löser upp alla så länge var och en är registrerad. Håll konstruktorerna fokuserade – för många parametrar är ett tecken på att klassen har för många ansvarsområden.
public class OrderService
{
public OrderService(
IOrderRepository orders,
IProductRepository products,
IEmailSender email,
ILogger<OrderService> logger)
{
// all injected by DI container
}
}Undvik anti-mönstret Service Locator
Injicera INTE IServiceProvider och anropa GetService i affärsmetoder. Det döljer beroenden och försvårar testning. Föredra konstruktorinjektion för kod som är tydlig och enkel att testa.
// BAD
public void Process(IServiceProvider sp)
{
var repo = sp.GetService<IProductRepository>(); // hidden dep!
}
// GOOD
public class Processor
{
private readonly IProductRepository _repo;
public Processor(IProductRepository repo) => _repo = repo;
}Syntax för primärkonstruktorer (C# 12)
C# 12 introducerar primärkonstruktorer för alla klasstyper, vilket gör att du kan deklarera parametrar direkt i klassdeklarationen och skriva mer kompakt DI-kod.
// C# 12 primary constructor
public class ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
public async Task<Product?> GetAsync(int id)
{
logger.LogInformation("Getting {Id}", id);
return await repo.GetByIdAsync(id);
}
}Byta implementationer
En viktig fördel med gränssnittsbaserad DI är att du kan byta implementation på en rad utan att ändra någon konsumerande kod. Det är användbart när du vill byta databas, e-postleverantör eller funktionsflagga.
// Switch from SQL to in-memory for integration tests
if (environment.IsEnvironment("Test"))
builder.Services.AddScoped<IProductRepository, InMemoryProductRepository>();
else
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();I praktiken: controller med DI
ASP.NET Core-controllers använder konstruktorinjektion direkt. Ramverket löser upp alla parametrar och skickar in dem.
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly ProductService _svc;
public ProductsController(ProductService svc) => _svc = svc;
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _svc.GetProductAsync(id);
return product is null ? NotFound() : Ok(product);
}
}Snabb kontroll
Vilken är den främsta fördelen med att programmera mot gränssnitt när du använder DI?
Sammanfattning: Konstruktorinjektion och gränssnitt
Viktiga lärdomar:
- Deklarera beroenden som konstruktorparametrar – containern tillhandahåller dem
- Definiera gränssnitt för tjänstekontrakt och injicera gränssnittet, inte den konkreta typen
- Gränssnitt gör det enkelt att använda mocks i enhetstester och byta implementationer
- Primärkonstruktorer i C# 12 minskar mängden standardkod i DI-tunga klasser
- Undvik anti-mönstret Service Locator – håll beroenden explicita
Lär dig C# med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 93
- Lektioner
- 346
Vanliga frågor
Är lektionen ”Konstruktorinjektion och gränssnitt” gratis?
Ja – hela texten till ”Konstruktorinjektion och gränssnitt” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i C# Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i C# Academy innehåller totalt 4 lektioner.
Vad lär jag mig i ”Konstruktorinjektion och gränssnitt”?
Använd konstruktorinjektion med gränssnittsbaserad design för att frikoppla komponenter och möjliggöra enhetstestning. Ni övar på C# Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig C# Academy?
Du behöver inga förkunskaper. Utbildningen i C# Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”Konstruktorinjektion och gränssnitt”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här C# Academy-lektionen?
Ja. Varje C# Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Grunderna i DI-containrar
- Tjänstelivslängder: Transient, Scoped, Singleton
- Konstruktorinjektion och gränssnitt
- Factory- och Options-mönster