ASP.NET Core with Needlr: Simplified Web Application Setup

Simplifying Web Application Dependency Injection

Building an ASP.NET Core web application traditionally involves configuring dependency injection in Program.cs, setting up middleware pipelines, and wiring up controllers or minimal API endpoints. Each of these steps requires manual service registration, and as your application grows, the startup code becomes a maintenance burden. ASP.NET Core with Needlr streamlines this process by providing a fluent API that automatically discovers and registers your services while producing a fully configured WebApplication ready to run.

Needlr extends its automatic service discovery to web applications through the ForWebApplication() method. Instead of manually calling builder.Services.AddControllers() and builder.Services.AddSingleton<IService, Service>(), you configure Needlr once and it handles the service registration automatically. This article walks through setting up an ASP.NET Core application with Needlr, configuring minimal APIs, working with middleware, and understanding how the web-specific integration differs from console applications.

If you are new to dependency injection in web applications, the guide on IServiceCollection in C# explains the fundamentals. For developers familiar with ASP.NET Core's built-in DI container, this article shows how Needlr simplifies the setup while maintaining compatibility with all standard ASP.NET Core features.

This article focuses specifically on web application setup. Topics like choosing between source generation and reflection, understanding the Syringe API, and configuring plugins are covered in other articles. Here we are concerned with getting a web application running with minimal boilerplate.

The ForWebApplication Entry Point

Needlr provides the ForWebApplication() method for ASP.NET Core applications. This method extends the base Syringe functionality with web-specific configuration and produces a WebApplication directly, eliminating the need to work with WebApplicationBuilder manually in most cases.

The basic setup is remarkably simple:

using NexusLabs.Needlr.AspNet;
using NexusLabs.Needlr.Injection;
using NexusLabs.Needlr.Injection.SourceGen;

var webApplication = new Syringe()
    .UsingSourceGen()              // Use source generation for scanning
    .ForWebApplication()            // Transition to web-specific configuration
    .BuildWebApplication();         // Build and return the WebApplication

await webApplication.RunAsync();

This single fluent chain replaces the traditional WebApplicationBuilder setup. Needlr automatically:

  • Creates the WebApplicationBuilder internally
  • Configures the service collection
  • Scans and registers your services
  • Builds the WebApplication
  • Returns it ready to run

The ForWebApplication() method transitions to web-specific configuration and returns a builder that produces a WebApplication. You can still configure discovery strategy and additional assemblies before calling ForWebApplication(), and you can use pre-registration callbacks to configure services.

Setting Up Minimal APIs

Minimal APIs are a lightweight way to build HTTP endpoints in ASP.NET Core. Needlr integrates seamlessly with minimal APIs by automatically discovering and registering your services, which you can then inject directly into endpoint handlers.

Here is a complete example of a minimal API application with Needlr:

using NexusLabs.Needlr.AspNet;
using NexusLabs.Needlr.Injection;
using NexusLabs.Needlr.Injection.SourceGen;

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

// Define endpoints with automatic dependency injection
webApplication.MapGet("/weather", (WeatherService weatherService) =>
{
    return Results.Ok(weatherService.GetCurrentWeather());
});

webApplication.MapPost("/orders", async (
    OrderService orderService,
    CreateOrderRequest request) =>
{
    var order = await orderService.CreateOrderAsync(request);
    return Results.Created($"/orders/{order.Id}", order);
});

await webApplication.RunAsync();

// Service implementations are automatically discovered and registered
public interface IWeatherService
{
    WeatherData GetCurrentWeather();
}

public class WeatherService : IWeatherService
{
    public WeatherData GetCurrentWeather()
    {
        return new WeatherData
        {
            TemperatureC = 22,
            Summary = "Sunny"
        };
    }
}

public interface IOrderService
{
    Task<Order> CreateOrderAsync(CreateOrderRequest request);
}

public class OrderService : IOrderService
{
    private readonly ILogger<OrderService> _logger;

    public OrderService(ILogger<OrderService> logger)
    {
        _logger = logger;
    }

    public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
    {
        _logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
        // Implementation...
        return await Task.FromResult(new Order { Id = Guid.NewGuid() });
    }
}

In this example, WeatherService and OrderService are automatically discovered and registered by Needlr. When the endpoints are invoked, ASP.NET Core's dependency injection system resolves these services and injects them into the endpoint handlers. You do not need to write any registration code.

Configuring Web Application Options

Before calling ForWebApplication(), you can use UsingPreRegistrationCallback to configure services that need to be registered before auto-discovery runs. After calling ForWebApplication(), you can use UsingConfigurationCallback to configure the WebApplicationBuilder, set up logging, configure hosting options, and more.

Here is an example that shows various configuration options:

using NexusLabs.Needlr.AspNet;
using NexusLabs.Needlr.Injection;
using NexusLabs.Needlr.Injection.SourceGen;

var webApplication = new Syringe()
    .UsingSourceGen()
    .ForWebApplication()
    .UsingOptions(() => CreateWebApplicationOptions
        .Default
        .UsingStartupConsoleLogger())  // Configure logging
    .BuildWebApplication();

// Additional configuration can be done on the WebApplication itself
webApplication.UseHttpsRedirection();
webApplication.UseAuthorization();

webApplication.MapGet("/", () => "Hello, World!");

await webApplication.RunAsync();

The UsingOptions() method allows you to customize the WebApplicationBuilder configuration. This is useful when you need to:

  • Configure logging providers
  • Set up hosting options
  • Configure environment-specific settings
  • Add additional service registrations manually

Working with Middleware

Middleware in ASP.NET Core is configured on the WebApplication instance after it is built. Needlr does not interfere with middleware configuration, so you can use all standard ASP.NET Core middleware patterns.

Here is an example that shows middleware configuration with Needlr:

using NexusLabs.Needlr.AspNet;
using NexusLabs.Needlr.Injection;
using NexusLabs.Needlr.Injection.SourceGen;

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

// Configure middleware pipeline
webApplication.UseHttpsRedirection();
webApplication.UseStaticFiles();
webApplication.UseRouting();
webApplication.UseAuthentication();
webApplication.UseAuthorization();

// Define endpoints
webApplication.MapGet("/api/health", () => Results.Ok(new { Status = "Healthy" }));

await webApplication.RunAsync();

Middleware that requires services from the dependency injection container can access them through the WebApplication.Services property or by using factory-based middleware registration. Needlr's automatic service discovery ensures that all your services are available to middleware just as they would be with manual registration.

Using Plugins for Web-Specific Configuration

Needlr's plugin system is particularly useful for web applications. You can create plugins that configure web-specific features like authentication, CORS, API versioning, or custom middleware.

Here is an example of a web application plugin:

using NexusLabs.Needlr;
using NexusLabs.Needlr.AspNet;

// Plugin that configures CORS and API endpoints
internal sealed class ApiPlugin : IWebApplicationPlugin
{
    public void Configure(WebApplicationPluginOptions options)
    {
        var app = options.WebApplication;

        // Configure CORS
        app.UseCors(builder => builder
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());

        // Define API endpoints
        app.MapGet("/api/products", (IProductService productService) =>
        {
            return Results.Ok(productService.GetAllProducts());
        });

        app.MapGet("/api/products/{id:int}", (int id, IProductService productService) =>
        {
            var product = productService.GetProductById(id);
            return product != null 
                ? Results.Ok(product) 
                : Results.NotFound();
        });
    }
}

// Service that gets automatically discovered
public interface IProductService
{
    IEnumerable<Product> GetAllProducts();
    Product? GetProductById(int id);
}

public class ProductService : IProductService
{
    private readonly List<Product> _products = new()
    {
        new Product { Id = 1, Name = "Widget", Price = 9.99m },
        new Product { Id = 2, Name = "Gadget", Price = 19.99m }
    };

    public IEnumerable<Product> GetAllProducts() => _products;

    public Product? GetProductById(int id) => _products.FirstOrDefault(p => p.Id == id);
}

The ApiPlugin is automatically discovered by Needlr and its Configure method is called after the WebApplication is built. This allows you to organize web-specific configuration into modular plugins, keeping your main startup code clean and focused.

Integration with Controllers

While minimal APIs are the modern approach, Needlr works equally well with traditional MVC controllers. Controllers are automatically discovered and registered, and their dependencies are injected automatically.

Here is an example using controllers:

using NexusLabs.Needlr.AspNet;
using NexusLabs.Needlr.Injection;
using NexusLabs.Needlr.Injection.SourceGen;
using Microsoft.AspNetCore.Mvc;

var webApplication = new Syringe()
    .UsingSourceGen()
    .ForWebApplication()
    .UsingOptions(() => CreateWebApplicationOptions
        .Default)
    .BuildWebApplication();

// Add controllers support
webApplication.MapControllers();

await webApplication.RunAsync();

// Controller with automatic dependency injection
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;
    private readonly ILogger<OrdersController> _logger;

    public OrdersController(
        IOrderService orderService,
        ILogger<OrdersController> logger)
    {
        _orderService = orderService;
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetOrder(int id)
    {
        var order = await _orderService.GetOrderByIdAsync(id);
        if (order == null)
        {
            return NotFound();
        }
        return Ok(order);
    }

    [HttpPost]
    public async Task<ActionResult<Order>> CreateOrder(CreateOrderRequest request)
    {
        var order = await _orderService.CreateOrderAsync(request);
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }
}

The IOrderService is automatically discovered and registered by Needlr, so it is available for injection into the controller. You do not need to manually register controllers or their dependencies.

Configuration and Options Pattern

ASP.NET Core's options pattern works seamlessly with Needlr. Configuration classes are automatically discovered and can be registered, and the IConfiguration service is available for injection.

Here is an example that shows configuration integration:

using NexusLabs.Needlr.AspNet;
using NexusLabs.Needlr.Injection;
using NexusLabs.Needlr.Injection.SourceGen;
using Microsoft.Extensions.Options;

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

webApplication.MapGet("/config", (IOptions<AppSettings> options) =>
{
    return Results.Ok(options.Value);
});

await webApplication.RunAsync();

// Configuration class
public class AppSettings
{
    public string ApplicationName { get; set; } = "My App";
    public int MaxRetries { get; set; } = 3;
}

// Service that uses configuration
public interface IAppConfigService
{
    AppSettings GetSettings();
}

public class AppConfigService : IAppConfigService
{
    private readonly IOptions<AppSettings> _options;

    public AppConfigService(IOptions<AppSettings> options)
    {
        _options = options;
    }

    public AppSettings GetSettings() => _options.Value;
}

Needlr automatically registers AppSettings and makes it available through the options pattern. The IConfiguration service is also registered automatically by ASP.NET Core, so you can inject it into any service that needs direct configuration access.

Real-World Example: E-Commerce API

Let's look at a more complete example that demonstrates a realistic web application setup:

using NexusLabs.Needlr.AspNet;
using NexusLabs.Needlr.Injection;
using NexusLabs.Needlr.Injection.SourceGen;
using Microsoft.AspNetCore.Mvc;

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

// Configure middleware
webApplication.UseHttpsRedirection();
webApplication.UseAuthentication();
webApplication.UseAuthorization();
webApplication.MapControllers();

await webApplication.RunAsync();

// Domain services (automatically discovered)
public interface IProductService
{
    Task<IEnumerable<Product>> GetProductsAsync();
    Task<Product?> GetProductByIdAsync(int id);
}

public class ProductService : IProductService
{
    private readonly IProductRepository _repository;
    private readonly ILogger<ProductService> _logger;

    public ProductService(
        IProductRepository repository,
        ILogger<ProductService> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task<IEnumerable<Product>> GetProductsAsync()
    {
        _logger.LogInformation("Retrieving all products");
        return await _repository.GetAllAsync();
    }

    public async Task<Product?> GetProductByIdAsync(int id)
    {
        _logger.LogInformation("Retrieving product {ProductId}", id);
        return await _repository.GetByIdAsync(id);
    }
}

public interface IProductRepository
{
    Task<IEnumerable<Product>> GetAllAsync();
    Task<Product?> GetByIdAsync(int id);
}

public class SqlProductRepository : IProductRepository
{
    private readonly IDbConnection _connection;

    public SqlProductRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        // Database query implementation
        return await Task.FromResult(Enumerable.Empty<Product>());
    }

    public async Task<Product?> GetByIdAsync(int id)
    {
        // Database query implementation
        return await Task.FromResult<Product?>(null);
    }
}

// API Controller (automatically discovered)
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        var products = await _productService.GetProductsAsync();
        return Ok(products);
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _productService.GetProductByIdAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }
}

In this example, all services are automatically discovered and registered:

  • ProductService is registered as IProductService
  • SqlProductRepository is registered as IProductRepository
  • ProductsController is registered automatically by ASP.NET Core
  • All dependencies are injected automatically

The entire dependency graph is wired up automatically. You write the business logic, and Needlr handles the infrastructure.

Comparison with Traditional ASP.NET Core Setup

Traditional ASP.NET Core setup requires manual service registration:

// Traditional approach
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddSingleton<IProductService, ProductService>();
builder.Services.AddSingleton<IProductRepository, SqlProductRepository>();
// ... dozens more registrations

var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

await app.RunAsync();

With Needlr, the service registrations are eliminated:

// Needlr approach
var webApplication = new Syringe()
    .UsingSourceGen()
    .ForWebApplication()
    .BuildWebApplication();

webApplication.UseHttpsRedirection();
webApplication.UseAuthorization();
webApplication.MapControllers();

await webApplication.RunAsync();

The difference is clear: Needlr removes the boilerplate of manual service registration while maintaining full compatibility with ASP.NET Core's features.

Conclusion

ASP.NET Core integration is one of Needlr's strongest features. By providing a fluent API that produces a fully configured WebApplication, Needlr eliminates the manual service registration boilerplate that plagues traditional ASP.NET Core applications. Whether you are building minimal APIs, MVC controllers, or a combination of both, Needlr's automatic service discovery ensures that your services are registered and available for injection without writing registration code.

The ForWebApplication() method extends Needlr's core functionality with web-specific configuration while maintaining compatibility with all standard ASP.NET Core features. Middleware, controllers, options, and configuration all work exactly as they do with manual registration, but without the maintenance burden.

Frequently Asked Questions

Can I use Needlr with existing ASP.NET Core applications?

Yes, Needlr works alongside existing manual registrations. You can gradually migrate to automatic discovery for new services while maintaining manual registrations for services that need special configuration.

Does Needlr support all ASP.NET Core features?

Yes, Needlr is built on top of the standard IServiceCollection and WebApplicationBuilder, so it supports all ASP.NET Core features including middleware, controllers, minimal APIs, authentication, authorization, and more.

How do I configure middleware with Needlr?

Middleware is configured on the WebApplication instance after it is built, exactly as it is with traditional ASP.NET Core setup. Needlr does not interfere with middleware configuration.

Can I use plugins to organize web-specific configuration?

Yes, Needlr's plugin system is particularly useful for web applications. You can create IWebApplicationPlugin implementations that configure endpoints, middleware, or other web-specific features.

Does Needlr work with both minimal APIs and controllers?

Yes, Needlr works with both minimal APIs and traditional MVC controllers. Services are automatically discovered and available for injection in both approaches.

How do I configure logging or other WebApplicationBuilder options?

Use the UsingPreRegistrationCallback method BEFORE calling ForWebApplication() to customize the service collection configuration before auto-discovery runs. Alternatively, use UsingConfigurationCallback AFTER ForWebApplication() to configure the WebApplicationBuilder.

Can I still manually register services if needed?

Yes, you can combine automatic discovery with manual registration. Use [DoNotAutoRegister] to exclude types from automatic discovery and register them manually with custom configuration.

ASP.NET Core for Beginners - What You Need To Get Started

Interested in building web applications? ASP.NET Core is a powerful dotnet tech stack for just that! Here are all of the details for ASP.NET Core for beginners!

Ultimate Starter Guide to Middleware in ASP.NET Core: Everything You Need to Know

Discover the benefits of middleware in ASP.NET Core, including flexibility and modularity. Learn about middleware like authentication and logging!

Custom Middleware in ASP.NET Core - How to Harness the Power!

Learn about different types of middleware and how to implement custom middleware in ASP.NET Core to solve common challenges in web development!

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