Skip to main content

🚀 Quick Start

This walkthrough builds a minimal but production-shaped setup using GenDI's idiomatic property injection style.

🎯 Step 1: Define contracts

Mark the interfaces you want injected with [ServiceInjection]:

[ServiceInjection]
public interface IInvoiceService
{
Task GenerateAsync(Guid invoiceId, CancellationToken ct = default);
}

[ServiceInjection]
public interface IClock
{
DateTimeOffset UtcNow { get; }
}

🏗️ Step 2: Implement with property injection

Declare dependencies as required init-only properties. No constructor needed:

[Injectable<IClock>(ServiceLifetime.Singleton)]
public sealed class SystemClock : IClock
{
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
}

[Injectable<IInvoiceService>(ServiceLifetime.Scoped, Group = 10, Order = 1)]
public sealed class InvoiceService : IInvoiceService
{
[Inject] public required IClock Clock { get; init; }
[Inject] public required ILogger<InvoiceService> Logger { get; init; }

public Task GenerateAsync(Guid invoiceId, CancellationToken ct = default)
{
Logger.LogInformation("Generating invoice {Id} at {UtcNow}", invoiceId, Clock.UtcNow);
return Task.CompletedTask;
}
}

Each [Inject] property replaces a constructor parameter + a private field + an assignment — all at once.

⚡ Step 3: Register generated services

One call wires everything GenDI discovered at compile time:

using MyProject.DependencyInjection;

builder.Services.AddGenDIServices();

▶️ Step 4: Consume

var service = provider.GetRequiredService<IInvoiceService>();
await service.GenerateAsync(Guid.NewGuid());

🏆 Property injection vs constructor injection at a glance

Both styles work. Property injection shines as the number of dependencies grows:

// ❌ Constructor style — each new dependency touches three things:
// 1. a private backing field
// 2. a constructor parameter
// 3. an assignment inside the constructor
private readonly IClock _clock;
private readonly ILogger<InvoiceService> _logger;
public InvoiceService(IClock clock, ILogger<InvoiceService> logger)
{
_clock = clock;
_logger = logger;
}

// ✅ Property style — one [Inject] line per dependency, zero private fields
[Inject] public required IClock Clock { get; init; }
[Inject] public required ILogger<InvoiceService> Logger { get; init; }

🔑 Keyed services

Use Key on [Injectable] and [Inject] to work with keyed registrations:

[Injectable<IInvoiceService>(ServiceLifetime.Scoped, Key = "invoices")]
public sealed class InvoiceService : IInvoiceService
{
[Inject(Key = "invoices")] public required ILogger<InvoiceService> Logger { get; init; }
}

// Resolve by key:
var service = provider.GetRequiredKeyedService<IInvoiceService>("invoices");

📝 Notes

  • If no [ServiceInjection] contract is found, GenDI falls back to registering the concrete type as its own service.
  • [Inject] properties must be get; init; with public or internal visibility.
  • Constructor injection with [FromKeyedServices] is also fully supported alongside property injection.