Getting Started with Needlr: Fluent DI for .NET Applications

Why Fluent Dependency Injection Matters in .NET

Every .NET developer eventually hits the same wall. You open Program.cs or Startup.cs and find a block of services.AddSingleton<IFoo, Foo>() calls that stretches for dozens, sometimes hundreds, of lines. Each new class means another manual registration. Each refactored interface means hunting through startup code to update the wiring. Getting started with Needlr is about eliminating that boilerplate from the very first line of code you write in a new project, replacing it with a fluent API that discovers and registers your services automatically.

If you are unfamiliar with the concept of dependency injection or why it matters, the guide on what is inversion of control is a solid starting point. For developers who already understand constructor injection and the role of a DI container, this article will walk you through installing Needlr, making your first Syringe() call, and running a working application with automatic service registration. We will cover both console applications and ASP.NET Core web applications, keeping the scope focused on the essentials so you can be productive in minutes rather than hours.

This article intentionally stays within the boundaries of installation and first-run setup. Topics like decorators, keyed services, plugins, and advanced configuration are covered in other articles in this series. The goal here is to give you the shortest path from an empty project to a functioning application with Needlr handling your dependency injection.

Installing the NuGet Packages

Needlr is distributed as a family of NuGet packages under the NexusLabs.Needlr namespace. Which packages you install depends on your scanning strategy and application type. For this getting-started guide, we will focus on the source generation approach since it is the recommended default for new projects.

The two core packages you need for a console application are:

  • NexusLabs.Needlr.Injection -- the core abstractions and the Syringe entry point
  • NexusLabs.Needlr.Injection.SourceGen -- the Roslyn source generator that emits registration code at compile time

If you are building an ASP.NET Core web application, you will also want:

  • NexusLabs.Needlr.AspNet -- the web application integration that produces a configured WebApplication

Open your terminal in the project directory and run the following commands to install the packages:

# For a console application with source generation
dotnet add package NexusLabs.Needlr.Injection
dotnet add package NexusLabs.Needlr.Injection.SourceGen

# For an ASP.NET Core web application, add the AspNet package as well
dotnet add package NexusLabs.Needlr.AspNet

These commands pull the latest stable versions from NuGet and add them to your .csproj file. The source generation package includes a Roslyn analyzer that runs during compilation, so there is no additional tooling to install or configure. Once the packages are restored, you are ready to write your first Needlr configuration.

If you prefer a single package that bundles the most common configuration together, Needlr also offers NexusLabs.Needlr.Injection.Bundle. Installing this package gives you access to the .UsingAutoConfiguration() method, which applies sensible defaults in a single call. For this guide we will use the individual packages so you can see exactly what each piece does, but the bundle is a convenient shortcut once you are comfortable with the fundamentals.

Understanding the Syringe Entry Point

The central concept in Needlr is the Syringe class. If you have worked with IServiceCollection in C# directly, you know that it is the standard abstraction for registering services in the Microsoft DI container. Needlr does not replace IServiceCollection. Instead, it provides a fluent builder that populates it for you.

The pattern follows three steps. First, you create a new Syringe instance. Second, you choose a scanning strategy by calling a method like .UsingSourceGen(). Third, you call .Scan() to execute the discovery and populate your service collection. Everything chains together in a single fluent expression, which means your entire DI configuration can live in one readable statement.

The Three-Step Pattern

  1. Create: Instantiate new Syringe() to begin configuration
  2. Configure: Choose a scanning strategy (.UsingSourceGen() or .UsingReflection())
  3. Execute: Call .Scan(services) to discover and register types

This design keeps things predictable. You always start with new Syringe(), you always pick a strategy, and you always scan. There are no hidden global state mutations or ambient service locators. The fluent API guides you through the configuration in a natural order, and each method call returns a builder that exposes only the methods that make sense at that point in the chain. This makes it difficult to misconfigure the library and easy to discover what options are available through your IDE's autocomplete.

Benefits of the Fluent API

  • Predictable Flow: Always follows the same three-step pattern
  • Type Safety: IDE autocomplete guides you through valid options
  • Readable Configuration: Entire DI setup in a single chainable expression
  • No Global State: Explicit configuration without hidden mutations
  • Discoverable: Method names clearly indicate their purpose

Your First Console Application With Needlr

Let us build a minimal console application that uses Needlr to wire up a simple service. Start by creating a new console project and installing the packages:

dotnet new console -n NeedlrHelloWorld
cd NeedlrHelloWorld
dotnet add package NexusLabs.Needlr.Injection
dotnet add package NexusLabs.Needlr.Injection.SourceGen

With the project scaffolded, the next step is to create a service interface and implementation. This is where Needlr's auto-discovery shines. You do not need to register your services manually. You simply write your classes and interfaces, and Needlr finds them during scanning.

What Needlr Discovers Automatically

  • Concrete Classes: Every public, non-abstract class is registered as itself
  • Interface Implementations: Classes implementing interfaces are registered against those interfaces
  • Service Dependencies: Constructor dependencies are automatically resolved
  • Multiple Interfaces: Classes implementing multiple interfaces are registered for each interface

You simply write your code, and Needlr handles the registration automatically.

// IGreetingService.cs
public interface IGreetingService
{
    string GetGreeting(string name);
}

// GreetingService.cs
public class GreetingService : IGreetingService
{
    public string GetGreeting(string name)
    {
        return $"Hello, {name}! Welcome to Needlr.";
    }
}

This is a straightforward service with a single method. The GreetingService class implements IGreetingService, and that relationship is all Needlr needs to register the service automatically. When the source generator scans your assembly, it discovers GreetingService, sees that it implements IGreetingService, and emits the equivalent of services.AddSingleton<IGreetingService, GreetingService>() in the generated code. You never write that registration line yourself.

Now wire up the Program.cs file to use Needlr:

// Program.cs
using Microsoft.Extensions.DependencyInjection;
using NexusLabs.Needlr;

// Create the Syringe and configure source generation scanning
IServiceCollection services = new ServiceCollection();
new Syringe()
    .UsingSourceGen()
    .Scan(services);

// Build the service provider and resolve the greeting service
var provider = services.BuildServiceProvider();
var greetingService = provider.GetRequiredService<IGreetingService>();

// Use the service
Console.WriteLine(greetingService.GetGreeting("Developer"));

This is the complete Program.cs for a working Needlr console application. The new Syringe() call creates the entry point. The .UsingSourceGen() call selects the source generation scanning strategy, which is AOT-compatible and performs all discovery at compile time. The .Scan(services) call populates the IServiceCollection with every discovered type.

After scanning, you build the ServiceProvider exactly as you would in any Microsoft DI application and resolve services through GetRequiredService<T>(). The only difference from a standard setup is that you did not write any explicit registration lines. Needlr handled that for you.

If you have worked with IServiceCollection in console applications before, this pattern will feel familiar. The article on how to use IServiceCollection in console applications covers the underlying mechanics of building a service provider outside of a web host, which is exactly what we are doing here.

Your First Web Application With Needlr

For ASP.NET Core applications, Needlr provides a dedicated integration that produces a WebApplication directly from the fluent builder. This means you do not need to create a ServiceCollection yourself or call BuildServiceProvider(). Instead, the builder handles everything and returns the standard WebApplication that you use to map endpoints and middleware.

Here is a minimal ASP.NET Core setup:

// Program.cs for an ASP.NET Core web application
using NexusLabs.Needlr;

var app = new Syringe()
    .UsingSourceGen()
    .ForWebApplication()
    .BuildWebApplication();

app.MapGet("/", (IGreetingService greetingService) =>
{
    return greetingService.GetGreeting("World");
});

app.Run();

The .ForWebApplication() method tells Needlr that you are building a web application, and .BuildWebApplication() returns a fully configured WebApplication instance. All of your services, including IGreetingService from the previous example, are automatically registered and available for injection into your endpoint handlers.

This is one of the most compelling aspects of Needlr for web development. Your entire Program.cs stays concise regardless of how many services your application contains. As you add more interfaces and implementations to your project, Needlr discovers and registers them at compile time. You focus on defining your endpoints and writing business logic while the DI wiring takes care of itself.

The same IGreetingService and GreetingService classes from the console example work here without any changes. That is the benefit of convention-based auto-discovery: your service code is completely decoupled from how it gets registered. You can move services between projects, rename them, or add new interfaces, and Needlr adapts automatically.

How Auto-Registration Works Behind the Scenes

Understanding what Needlr does during scanning helps you predict its behavior and troubleshoot when something does not resolve as expected. The source generator follows a simple convention: every public concrete class in your scanned assemblies is registered as itself, and if that class implements one or more interfaces, it is also registered against each of those interfaces.

This means that if you write a class like this:

public class NotificationService : INotificationService, IDisposable
{
    public void Notify(string message)
    {
        Console.WriteLine($"Notification: {message}");
    }

    public void Dispose()
    {
        // Cleanup resources
    }
}

Needlr registers NotificationService as itself, as INotificationService, and as IDisposable. You can resolve it by any of those types. This is the same behavior you would get if you manually wrote three separate registration calls, but it happens automatically.

Not every class belongs in the DI container, though. Data transfer objects, entity classes, configuration POCOs, and similar types are not services and should not be registered. Needlr provides the [DoNotAutoRegister] attribute for exactly this purpose. Applying it to a class tells the scanner to skip it entirely:

using NexusLabs.Needlr;

[DoNotAutoRegister]
public class WeatherForecast
{
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public string Summary { get; set; }
}

The [DoNotAutoRegister] attribute is a clean opt-out mechanism. The default behavior is to register everything, and you mark the exceptions explicitly. For most applications, the number of types you need to exclude is small compared to the number of services that benefit from auto-registration. This keeps your codebase free of repetitive attribute annotations while still giving you control where it matters.

Choosing Between Source Generation and Reflection

Needlr supports two scanning strategies, and choosing between them is one of the first decisions you make when setting up the library. Source generation, accessed through .UsingSourceGen(), is the recommended default. It uses a Roslyn source generator to emit registration code at compile time. This means there is no reflection at runtime, the generated code is visible in your build output for inspection, and the approach is fully compatible with Native AOT compilation.

Reflection-based scanning, accessed through .UsingReflection(), performs discovery at application startup by inspecting assemblies at runtime. This strategy requires the NexusLabs.Needlr.Injection.Reflection package instead of the source gen package. The API surface is identical, and the same conventions apply. The difference is entirely in when the discovery happens: compile time versus runtime.

For this getting-started guide, source generation is the right choice unless you have a specific reason to use reflection. The compile-time approach catches registration issues earlier, produces faster startup times, and aligns with the direction that the .NET ecosystem is moving toward with AOT and trimming support. If you later find that you need reflection, switching is a one-line change from .UsingSourceGen() to .UsingReflection() and a NuGet package swap.

Verifying Your Setup

After writing your first Needlr application, it is worth verifying that everything is working correctly. Build your project and check that there are no compilation errors related to the source generator. If you are using an IDE like Visual Studio or Rider, the generated registration code should be visible under the source generators node in Solution Explorer.

Run the application and confirm that your services resolve correctly. For the console example, you should see the greeting message printed to the console. For the web application, navigate to the root URL and confirm the response contains the greeting. If a service fails to resolve, the most common causes are a missing NuGet package, a class that is not public, or an assembly that is not being scanned.

The official Needlr documentation covers troubleshooting in more detail, including how to inspect the generated source code and diagnose scanning issues. For now, the key takeaway is that if your classes are public, concrete, and in a scanned assembly, Needlr will find and register them.

What Comes Next After the Basics

With the fundamentals in place, there are several directions you can go. Needlr supports manual registration through IServiceCollectionPlugin for cases where auto-discovery is not enough. It provides decorators through [DecoratorFor<T>] for wrapping services with cross-cutting concerns. On .NET 8 and later, it integrates with keyed services for scenarios where multiple implementations of the same interface need to be distinguished by key.

These topics are intentionally outside the scope of this getting-started guide. The goal here was to get you from zero to a running application with the least amount of friction. If you are interested in how Needlr compares to other DI approaches like Autofac or Scrutor, the article on Scrutor vs Autofac in C# provides useful context for understanding where convention-based scanning fits in the broader ecosystem.

For developers coming from Autofac specifically, the article on dependency injection with Autofac explains the module-based approach that Autofac uses. Needlr takes a different path with its fluent API and convention-over-configuration philosophy, but the underlying problem both libraries solve is the same: reducing the amount of manual wiring that stands between you and productive development.

Frequently Asked Questions

What is the minimum .NET version required for Needlr?

Needlr targets modern .NET versions. The source generation package requires a version of .NET that supports Roslyn source generators, which means .NET 6 or later. The reflection-based package has similar requirements since it depends on Microsoft.Extensions.DependencyInjection abstractions. For the best experience, especially with features like keyed services, targeting .NET 8 or later is recommended. Check the Needlr GitHub repository for the latest compatibility information.

Do I need to change my existing service classes to use Needlr?

No. Needlr discovers and registers your classes based on conventions. If you have a public class that implements an interface, Needlr will find it and register it without any attributes or annotations. The only attribute you might use is [DoNotAutoRegister] to exclude specific types from scanning. Your existing service code, interfaces, and constructor injection patterns remain exactly the same.

Can I use Needlr with the .NET Generic Host?

Yes. Needlr works with any host that uses IServiceCollection for service registration. The .NET Generic Host, which is the foundation for console applications, background workers, and non-web services, exposes an IServiceCollection during configuration. You can call new Syringe().UsingSourceGen().Scan(services) with that collection to register your services. The article on how to use IServiceCollection in console applications explains how the Generic Host and IServiceCollection fit together.

What happens if I forget to install the source generation package?

If you install NexusLabs.Needlr.Injection but forget the NexusLabs.Needlr.Injection.SourceGen package, you will not have access to the .UsingSourceGen() method. Your project will fail to compile with a method-not-found error, which makes the issue easy to diagnose. This is one of the advantages of the compile-time approach: problems surface immediately rather than at runtime. Make sure both the core package and the scanning strategy package are installed together.

Is the source generation approach compatible with Native AOT?

Yes. Source generation is the recommended strategy specifically because it is compatible with Native AOT compilation. The Roslyn source generator emits plain C# registration code during compilation, so there is no runtime reflection involved. This means the .NET trimmer and AOT compiler can analyze the generated code just like any other code in your project. If AOT compatibility is important for your deployment scenario, source generation is the correct choice.

How does Needlr decide the service lifetime for auto-registered types?

Needlr's auto-discovery registers types with a default lifetime. If you need a specific lifetime such as singleton or scoped for a particular service, you have options to control that through Needlr's configuration. For the getting-started scope covered in this article, the default lifetime applies to all auto-discovered services. Advanced lifetime configuration is covered in the Needlr documentation.

Can I see what Needlr registers during scanning?

With the source generation approach, the generated registration code is part of your build output. In Visual Studio, you can expand the Analyzers node under Dependencies in Solution Explorer to find the generated files. In Rider, the source generators output is similarly accessible through the project tree. This visibility is one of the key benefits of source generation over reflection: you can inspect, search, and even set breakpoints in the generated code to understand exactly what Needlr is doing on your behalf.

Wrapping Up

Getting started with Needlr is a matter of installing two NuGet packages, writing new Syringe().UsingSourceGen().Scan(services), and letting the source generator handle the rest. Your public classes and interfaces are discovered and registered automatically, your Program.cs stays concise, and you spend your time writing business logic instead of wiring up a container.

The fluent API built around the Syringe class gives you a single, discoverable entry point that guides you through configuration. The source generation strategy keeps everything compile-time safe and AOT compatible. And because Needlr populates a standard IServiceCollection, everything you already know about the Microsoft DI container continues to apply.

From here, the next steps are to explore manual registrations with plugins, decorators for cross-cutting concerns, and keyed services for multi-implementation scenarios. But for most applications, what you have seen in this article is enough to get productive. Create your services, let Needlr find them, and focus on the code that matters.

IServiceCollection in C# - Simplified Beginner's Guide For Dependency Injection

Learn about IServiceCollection in C# and dependency injection. See how Dependency Inversion, Single Responsibility, and Open/Closed Principles fit together!

Using Autofac in C# - 3 Simple Tips for Beginners

Learn how to use Autofac in C# for dependency injection! Check out these three code examples to help you get started with dependency injection in C#.

Automatic Dependency Injection in C#: The Complete Guide to Needlr

Learn how Needlr simplifies dependency injection in C# with automatic service discovery, source generation, and a fluent API for .NET applications.

An error has occurred. This application may no longer respond until reloaded. Reload x