Introducing Injectlynx: Attribute-free Compile-time Dependency Injection For .net
Dependency Injection (DI) is one of the foundations of modern .NET development. Whether you're building ASP.NET Core APIs, Minimal APIs, Worker Services, or libraries, you're probably using Microsoft.Extensions.DependencyInjection.
It works well, but as applications grow, one challenge becomes obvious: service registration.
Most teams end up choosing one of two approaches.
The first is manual registration:
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IPaymentGateway, StripePaymentGateway>();
services.AddTransient<IRequestHandler<GetOrderQuery>, GetOrderQueryHandler>();
This is explicit and easy to understand, but maintaining hundreds of registrations quickly becomes repetitive and error-prone.
The second option is runtime assembly scanning:
services.Scan(scan => ...);
Assembly scanning removes much of the boilerplate, but it relies on runtime discovery and reflection. That can make applications harder to reason about, especially when working with trimming, Native AOT, or large modular solutions.
I wanted something that combined the best parts of both approaches:
- No repetitive manual registrations
- No attributes on every service class
- No runtime assembly scanning
- No custom DI container
- Full compatibility with Microsoft Dependency Injection
That idea became Injectlynx.
What is Injectlynx?
Injectlynx is an attribute-free, convention-based compile-time dependency injection toolkit for .NET.
Instead of discovering services at runtime, Injectlynx uses a Roslyn Incremental Source Generator to generate normal IServiceCollection registrations during compilation.
The generated code is exactly what you would write by hand.
There is:
- ✅ No runtime reflection for service discovery
- ✅ No assembly scanning
- ✅ No custom DI container
- ✅ No attributes on every service
- ✅ No startup performance penalty for service discovery
You continue using the standard Microsoft DI container.
Install it with:
dotnet add package Injectlynx
The Problem with Traditional Registration
Imagine a project with hundreds of services.
You eventually end up with code like this:
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<ICustomerService, CustomerService>();
services.AddScoped<IInvoiceService, InvoiceService>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IEmailService, EmailService>();
// ...
Besides being repetitive, this list needs constant maintenance.
Rename a service.
Move a namespace.
Add a new implementation.
It's easy to forget updating the registration.
The Injectlynx Way
Instead of registering every service manually, define your conventions once.
using Injectlynx;
namespace Shop.Application;
public static class ApplicationServiceConventions
{
public static void Configure(IServiceConventionBuilder services)
{
services
.FromNamespace("Shop.Application.Services")
.WhereNameEndsWith("Service")
.AsMatchingInterface()
.WithScopedLifetime();
}
}
That's it.
Now create a normal service.
public interface IOrderService
{
OrderSummary GetOrder(Guid id);
}
public sealed class OrderService : IOrderService
{
public OrderSummary GetOrder(Guid id)
{
return new(id, "Created");
}
}
During compilation, Injectlynx generates:
services.AddScoped<IOrderService, OrderService>();
No attributes.
No reflection.
No runtime scanning.
Keep Your Service Classes Clean
Many compile-time DI libraries require decorating every implementation.
For example:
[Scoped]
public sealed class OrderService
{
}
Injectlynx deliberately avoids that.
Your business classes stay focused on business logic instead of dependency injection metadata.
Works with Microsoft Dependency Injection
Injectlynx doesn't replace the built-in container.
The generated output is simply standard Microsoft DI code.
Startup remains familiar:
builder.Services.AddInjectlynxServices();
Or, in larger solutions:
builder.Services
.AddApplicationServices()
.AddInfrastructureServices();
The registration methods are generated automatically during build.
More Than Just Service Registration
Injectlynx also supports scenarios commonly found in enterprise applications.
Convention-based registration
Register services by namespace, naming pattern, or interface.
Open generic registration
Repository<TEntity> : IRepository<TEntity>
Generated automatically as:
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
Multiple modules
Each project can expose its own generated registration method.
builder.Services
.AddApplicationServices()
.AddInfrastructureServices()
.AddDomainServices();
Property and method injection
Constructor injection should always be the default.
However, property and method injection are available when working with legacy code, framework-created instances, optional dependencies, or initialization scenarios.
Build-time diagnostics
Injectlynx validates registrations during compilation and can detect issues before the application starts.
Examples include:
- Missing interfaces
- Duplicate registrations
- Invalid conventions
- Constructor issues
- Lifetime mismatches
- Circular dependencies
Native AOT Friendly
Modern .NET applications increasingly use trimming and Native AOT.
Because Injectlynx generates registration code during compilation instead of discovering services at runtime, applications remain predictable and easier to validate in AOT-friendly scenarios.
This makes it a good choice for:
- ASP.NET Core APIs
- Minimal APIs
- Worker Services
- Cloud-native applications
- Native AOT projects
- Large modular solutions
Why I Built Injectlynx
After working on large .NET projects, I noticed the same problems appearing repeatedly.
Manual registration became difficult to maintain.
Runtime scanning was convenient but less predictable for modern deployment models.
Attribute-based compile-time solutions scattered dependency injection metadata across hundreds of files.
I wanted a solution that kept service classes clean while giving the compiler enough information to generate registrations automatically.
Injectlynx is the result of that idea.
When Should You Use It?
Injectlynx is a good fit when:
- Your project follows consistent naming conventions.
- You want compile-time generated registrations.
- You don't want attributes on every service.
- You use the Microsoft DI container.
- You are building modular applications.
- You care about trimming or Native AOT.
- You want build-time validation instead of runtime surprises.
For very small projects with only a handful of services, manual registration is still perfectly reasonable.
If your application loads unknown plugin assemblies dynamically at runtime, runtime scanning may still be the better approach for that specific scenario.
Final Thoughts
Dependency injection registration shouldn't become a maintenance task.
Injectlynx keeps service classes clean, removes repetitive registration code, generates standard Microsoft DI registrations during compilation, and provides build-time validation without changing the way you build .NET applications.
The goal isn't to replace Microsoft's DI container—it's to make using it simpler, cleaner, and more predictable.
If you're building modern .NET applications and want compile-time registration without runtime scanning or service attributes, give Injectlynx a try.
GitHub: https://github.com/it-nilesh/Injectlynx
NuGet: https://www.nuget.org/packages/Injectlynx
If you find the project useful, I'd love to hear your feedback, feature ideas, or contributions. Every suggestion helps improve the developer experience for the .NET community.
Popular Products
-
Classic Oversized Teddy Bear$23.78 -
Gem's Ballet Natural Garnet Gemstone ...$171.56$85.78 -
Butt Lifting Body Shaper Shorts$95.56$47.78 -
Slimming Waist Trainer & Thigh Trimmer$67.56$33.78 -
Realistic Fake Poop Prank Toys$99.56$49.78