ServiceLocator 2.0.3
dotnet add package ServiceLocator --version 2.0.3
NuGet\Install-Package ServiceLocator -Version 2.0.3
<PackageReference Include="ServiceLocator" Version="2.0.3" />
<PackageVersion Include="ServiceLocator" Version="2.0.3" />
<PackageReference Include="ServiceLocator" />
paket add ServiceLocator --version 2.0.3
#r "nuget: ServiceLocator, 2.0.3"
#:package ServiceLocator@2.0.3
#addin nuget:?package=ServiceLocator&version=2.0.3
#tool nuget:?package=ServiceLocator&version=2.0.3
ServiceLocator
Attribute-based service registration for .NET dependency injection with support for keyed services. Automatically scan and register services decorated with the [Service] attribute.
Features
- 🚀 Automatic Service Registration - Decorate classes with
[Service]and they're automatically registered - 🔑 Keyed Services Support - Register and resolve services by key
- ⚡ All Service Lifetimes - Scoped, Singleton, and Transient support
- 🎯 Interface Registration - Services registered as both concrete type and all interfaces
- 🔍 Assembly Scanning - Efficient reflection-based service discovery
- ✨ Clean API - Single extension method:
AddServiceLocator<T>() - 📦 Lightweight - Zero external dependencies (except Microsoft.Extensions.DependencyInjection)
Installation
dotnet add package ServiceLocator
Requirements:
- .NET 9.0 or later
- Microsoft.Extensions.DependencyInjection 9.0.0+
Quick Start
Basic Usage (Non-Keyed Services)
1. Decorate your services:
using Microsoft.Extensions.DependencyInjection;
using ServiceLocator;
public interface IUserService
{
User GetUser(int id);
}
[Service(ServiceLifetime.Scoped)]
public class UserService : IUserService
{
public User GetUser(int id) => new User { Id = id };
}
2. Register services in your application:
var builder = WebApplication.CreateBuilder(args);
// Automatically register all services with [Service] attribute
// from the assembly containing Program
builder.Services.AddServiceLocator<Program>();
var app = builder.Build();
3. Use dependency injection as normal:
app.MapGet("/user/{id}", (int id, IUserService userService) =>
{
return userService.GetUser(id);
});
app.Run();
Keyed Services (New in v2.0.0)
Keyed services allow you to register multiple implementations of the same interface and resolve them by key.
1. Register services with keys:
public interface ICache
{
string Get(string key);
}
[Service(ServiceLifetime.Singleton, Key = "redis")]
public class RedisCache : ICache
{
public string Get(string key) => $"Redis: {key}";
}
[Service(ServiceLifetime.Singleton, Key = "memory")]
public class MemoryCache : ICache
{
public string Get(string key) => $"Memory: {key}";
}
2. Resolve services by key:
using Microsoft.AspNetCore.Mvc;
app.MapGet("/cache/redis/{key}", (
string key,
[FromKeyedServices("redis")] ICache cache) =>
{
return cache.Get(key);
});
app.MapGet("/cache/memory/{key}", (
string key,
[FromKeyedServices("memory")] ICache cache) =>
{
return cache.Get(key);
});
3. Enumerate all services (including keyed):
app.MapGet("/cache/all/{key}", (
string key,
IEnumerable<ICache> caches) =>
{
return caches.Select(c => c.Get(key));
});
// Returns: ["Redis: test", "Memory: test"]
Service Lifetimes
Scoped
Services are created once per request (HTTP request in web apps).
[Service(ServiceLifetime.Scoped)]
public class RequestService : IRequestService
{
// New instance per HTTP request
}
Singleton
Services are created once and shared across the entire application lifetime.
[Service(ServiceLifetime.Singleton)]
public class ConfigurationService : IConfigurationService
{
// Single instance for entire application
}
Transient
Services are created each time they are requested.
[Service(ServiceLifetime.Transient)]
public class TemporaryService : ITemporaryService
{
// New instance every time it's injected
}
Advanced Scenarios
Multiple Implementations
Register multiple implementations of the same interface:
public interface INotificationService
{
void Notify(string message);
}
[Service(ServiceLifetime.Scoped)]
public class EmailNotificationService : INotificationService
{
public void Notify(string message) => SendEmail(message);
}
[Service(ServiceLifetime.Scoped)]
public class SmsNotificationService : INotificationService
{
public void Notify(string message) => SendSms(message);
}
// Inject all implementations
app.MapPost("/notify", (string message, IEnumerable<INotificationService> notifiers) =>
{
foreach (var notifier in notifiers)
{
notifier.Notify(message);
}
});
Enum Keys
Keys can be strings, integers, enums, or any object:
public enum CacheType
{
Primary,
Secondary,
Fallback
}
[Service(ServiceLifetime.Singleton, Key = CacheType.Primary)]
public class PrimaryCache : ICache
{
// ...
}
// Resolve by enum
app.MapGet("/cache/primary", ([FromKeyedServices(CacheType.Primary)] ICache cache) =>
{
return cache.Get("data");
});
Mixed Keyed and Non-Keyed Services
// Non-keyed service (traditional registration)
[Service(ServiceLifetime.Scoped)]
public class DefaultCache : ICache
{
public string Get(string key) => $"Default: {key}";
}
// Keyed services
[Service(ServiceLifetime.Scoped, Key = "fast")]
public class FastCache : ICache
{
public string Get(string key) => $"Fast: {key}";
}
// Resolve non-keyed service
app.MapGet("/cache/default/{key}", (string key, ICache cache) =>
{
return cache.Get(key); // Gets DefaultCache
});
// Resolve keyed service
app.MapGet("/cache/fast/{key}", (
string key,
[FromKeyedServices("fast")] ICache cache) =>
{
return cache.Get(key); // Gets FastCache
});
// Enumerate all (both keyed and non-keyed)
app.MapGet("/cache/all/{key}", (string key, IEnumerable<ICache> caches) =>
{
return caches.Select(c => c.Get(key));
// Returns: ["Default: test", "Fast: test"]
});
How It Works
ServiceLocator uses a unified reflection-based registration process:
- Scans the assembly for all classes decorated with
[Service]attribute - Non-Keyed Services (where
Key == null):- Registered as concrete type (first-wins behavior with
TryAdd*) - Registered for all implemented interfaces (allows multiple implementations with
TryAddEnumerable)
- Registered as concrete type (first-wins behavior with
- Keyed Services (where
Key != null):- Registered with the specified key using
AddKeyedScoped/Singleton/Transient - Also registered as non-keyed for enumeration support via
IEnumerable<T>
- Registered with the specified key using
Important: Keyed services are accessible both by key AND via enumeration, giving you maximum flexibility.
Migration from v1.x to v2.0.0
Breaking Changes
- Target Framework: Now requires .NET 9.0 (previously .NET 7.0)
- Dependency Removed: Scrutor is no longer a dependency (reduces package size)
Non-Breaking Changes
[Service]attribute is backward compatible - existing code works without changes- New optional
Keyproperty added for keyed service support - All existing tests pass without modification
Upgrading
Update your project to target .NET 9.0:
<TargetFramework>net9.0</TargetFramework>Update the package:
dotnet add package ServiceLocator --version 2.0.3(Optional) Start using keyed services for new scenarios
Dependencies
Examples
See the ServiceLocator.TestApi project for complete working examples of:
- Scoped, Singleton, and Transient services
- Multiple implementations of the same interface
- Keyed services with string keys
- Enumerating all services including keyed ones
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
With love from Courland IT ❤️
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net9.0
- Microsoft.Extensions.DependencyInjection (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ServiceLocator:
| Package | Downloads |
|---|---|
|
HotCode.System
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.