EasyServiceRegister 3.1.1

dotnet add package EasyServiceRegister --version 3.1.1
                    
NuGet\Install-Package EasyServiceRegister -Version 3.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EasyServiceRegister" Version="3.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EasyServiceRegister" Version="3.1.1" />
                    
Directory.Packages.props
<PackageReference Include="EasyServiceRegister" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EasyServiceRegister --version 3.1.1
                    
#r "nuget: EasyServiceRegister, 3.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EasyServiceRegister@3.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EasyServiceRegister&version=3.1.1
                    
Install as a Cake Addin
#tool nuget:?package=EasyServiceRegister&version=3.1.1
                    
Install as a Cake Tool

EasyServiceRegister

Simple, Attribute-Based Dependency Injection for .NET

EasyServiceRegister is a lightweight library built on top of Microsoft.Extensions.DependencyInjection.Abstractions. It simplifies service registration using attributes — supporting all lifetimes, keyed services (.NET 8+), the decorator pattern, startup validation, and registration diagnostics.

Targets: netstandard2.1 · net8.0 · net9.0


Installation

dotnet add package EasyServiceRegister

Quick Start

// 1. Decorate your services
[RegisterAsScoped]
public class ProductService : IProductService
{
    public Task<Product> CreateProduct(Product product) { /* ... */ }
}

// 2. Register and validate at startup
builder.Services
    .AddServices(typeof(Program))
    .EnsureServicesAreValid();

That's it. No manual services.AddScoped<...>() calls needed.


Registration Attributes

Attribute Lifetime Keyed
[RegisterAsSingleton] Singleton No
[RegisterAsScoped] Scoped No
[RegisterAsTransient] Transient No
[RegisterAsSingletonKeyed("key")] Singleton Yes (.NET 8+)
[RegisterAsScopedKeyed("key")] Scoped Yes (.NET 8+)
[RegisterAsTransientKeyed("key")] Transient Yes (.NET 8+)

Attribute Parameters

All registration attributes share these optional parameters:

Parameter Default Description
serviceInterface null Specific interface to register the service as
useTryAdd* false Use TryAdd instead of Add (won't override existing registrations)
registerAsAllInterfaces true Register the service against all implemented interfaces

Keyed attributes also require a key parameter (any object: string, enum, etc.).

Specifying a Service Interface

When a class implements multiple interfaces, you can target a specific one:

[RegisterAsScoped(serviceInterface: typeof(IOrderService))]
public class OrderService : IOrderService, IDisposable
{
    // Registered only as IOrderService
}

Registering as All Interfaces

[RegisterAsScoped]
public class NotificationService : IEmailNotifier, ISmsNotifier
{
    // Registered as both IEmailNotifier and ISmsNotifier
}

Open Generic Support

[RegisterAsScoped]
public class Repository<T> : IRepository<T>
{
    // Registered as IRepository<> (open generic)
}

Using TryAdd

[RegisterAsSingleton(useTryAddSingleton: true)]
public class CacheProvider : ICacheProvider
{
    // Only registered if ICacheProvider isn't already in the container
}

Scanning and Registration

Call AddServices in your Program.cs or Startup.cs:

// Scan one or more assemblies via marker types
services.AddServices(typeof(Program));
services.AddServices(typeof(MarkerA), typeof(MarkerB));

// Or pass assemblies directly
services.AddServices(typeof(MarkerA).Assembly);

Filtering Types

services.AddServices(
    filter: type => type.Namespace.Contains("Services"),
    typeof(Program));

The filter receives a TypeInfo and returns false to skip registration.


Keyed Services (.NET 8+)

[RegisterAsSingletonKeyed("primary")]
public class PrimaryEmailService : IEmailService { }

[RegisterAsSingletonKeyed("secondary")]
public class SecondaryEmailService : IEmailService { }

Resolve by key using [FromKeyedServices]:

public class EmailManager
{
    public EmailManager(
        [FromKeyedServices("primary")] IEmailService primary,
        [FromKeyedServices("secondary")] IEmailService secondary)
    { }
}

Decorator Pattern

Layer cross-cutting concerns using [DecorateWith]. Multiple decorators can be applied and are ordered by the order parameter (lower values wrap first):

[RegisterAsScoped]
[DecorateWith(typeof(LoggingDecorator), order: 0)]
[DecorateWith(typeof(CachingDecorator), order: 1)]
public class ProductService : IProductService
{
    public Product GetById(int id) { /* ... */ }
}

Each decorator must implement the same interface and receive the inner service through its constructor:

public class LoggingDecorator : IProductService
{
    private readonly IProductService _inner;
    private readonly ILogger<LoggingDecorator> _logger;

    public LoggingDecorator(IProductService inner, ILogger<LoggingDecorator> logger)
    {
        _inner = inner;
        _logger = logger;
    }

    public Product GetById(int id)
    {
        _logger.LogInformation("Getting product {Id}", id);
        return _inner.GetById(id);
    }
}

Resolution chain: LoggingDecoratorCachingDecoratorProductService


Startup Validation

EasyServiceRegister includes a validation system that detects common DI misconfigurations at startup — before they become runtime exceptions.

Validation runs against all services in the container, not just those registered through EasyServiceRegister. Framework-internal services (types under System.* and Microsoft.* namespaces) are automatically excluded to avoid false positives.

Setup

builder.Services
    .AddServices(typeof(Program))
    .EnsureServicesAreValid(); // Throws ServiceValidationException on errors

EnsureServicesAreValid() throws a ServiceValidationException if any errors are detected. Warnings are included in the exception details but don't trigger a throw on their own. The method returns IServiceCollection for chaining.

What It Detects

Issue Severity Description
Missing dependencies Error A service's constructor requires a type that isn't registered in the container
Scoped in singleton Error A singleton depends on a scoped service — the DI container will throw at runtime
Captive dependency chain Error A singleton → singleton → scoped chain silently captures a scoped service
Circular dependencies Error Cyclic dependency graphs (A → B → C → A)
Disposable transients Warning A transient implements IDisposable but won't be disposed by the container, causing potential memory/resource leaks
Transient in singleton Warning A singleton depends on a transient — the transient is instantiated only once and reused, losing its intended lifetime semantics

Advanced Usage

For programmatic access without throwing, use ValidateServices() directly:

var issues = builder.Services.ValidateServices();

foreach (var issue in issues)
{
    Console.WriteLine($"[{issue.Severity}] {issue.Message}");
    // issue.ServiceType and issue.ImplementationType available for programmatic handling
}

Filter by severity:

var errorsOnly = builder.Services.ValidateServices(minimumSeverity: ValidationSeverity.Error);

Handling the Exception

try
{
    builder.Services.EnsureServicesAreValid();
}
catch (ServiceValidationException ex)
{
    foreach (var issue in ex.Issues)
    {
        Console.WriteLine($"[{issue.Severity}] {issue.Message}");
    }
}

Registration Diagnostics

Query what was registered through EasyServiceRegister at runtime:

var all = ServicesExtension.GetRegisteredServices();

foreach (var svc in all)
{
    Console.WriteLine($"{svc.ServiceType.Name} → {svc.ImplementationType.Name} [{svc.Lifetime}]");
    Console.WriteLine($"  Method: {svc.RegistrationMethod}, Attribute: {svc.AttributeUsed}, Key: {svc.ServiceKey}");

    foreach (var dec in svc.Decorators)
    {
        Console.WriteLine($"  Decorator: {dec.DecoratorType.Name} (order: {dec.Order})");
    }
}

Filter by type or lifetime:

var singletons = ServicesExtension.GetRegisteredServices(lifetime: ServiceLifetime.Singleton);
var specific = ServicesExtension.GetRegisteredServices(serviceType: typeof(IProductService));

Clear the log (useful in test scenarios):

ServicesExtension.ClearRegistrationLog();

License

MIT ©

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.1.1 0 3/30/2026
3.1.0 5 3/30/2026
3.0.5 950 12/5/2025
3.0.4 579 11/17/2025
3.0.3 675 8/3/2025
3.0.2 212 7/5/2025
3.0.1 139 7/4/2025
3.0.0 143 7/4/2025
2.2.1 320 6/1/2025
2.2.0 189 6/1/2025
2.1.0 564 5/1/2025
2.0.9 7,741 12/6/2023
2.0.8 206 12/5/2023
2.0.7 153 12/5/2023
2.0.6 875 4/29/2023
2.0.5 2,772 11/26/2022
2.0.4 430 11/23/2022
2.0.3 466 11/20/2022
2.0.2 434 11/19/2022
2.0.1 413 11/19/2022
Loading failed