Muslim.Inject
2.2.0
See the version list below for details.
dotnet add package Muslim.Inject --version 2.2.0
NuGet\Install-Package Muslim.Inject -Version 2.2.0
<PackageReference Include="Muslim.Inject" Version="2.2.0" />
<PackageVersion Include="Muslim.Inject" Version="2.2.0" />
<PackageReference Include="Muslim.Inject" />
paket add Muslim.Inject --version 2.2.0
#r "nuget: Muslim.Inject, 2.2.0"
#:package Muslim.Inject@2.2.0
#addin nuget:?package=Muslim.Inject&version=2.2.0
#tool nuget:?package=Muslim.Inject&version=2.2.0
Muslim.Inject
Convention-based dependency registration for Microsoft.Extensions.DependencyInjection.
Mark an interface, and it registers itself:
public interface IUserService : IAutoInjection { }
public class UserService : IUserService { }
builder.Services.AddAutoInjection(typeof(IUserService).Assembly);
That is the whole wiring. No AddScoped per service, no registration file to keep in sync.
dotnet add package Muslim.Inject
Setup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAutoInjection(
typeof(IUserService).Assembly, // Application
typeof(UserRepository).Assembly); // Data
var app = builder.Build();
InjectExtensions.SetServiceProvider(app.Services);
Pass the assemblies that hold your services. Scanning everything loaded is slow and drags in framework types.
Pass
app.Services, notservices.BuildServiceProvider(). The latter builds a second container from the same collection: singletons get duplicated — one set resolved by the framework, another by this one — and the host no longer controls their disposal.
Per-request scopes
If you use Inject<T>() (below), point it at the request's scope:
app.Use(async (context, next) =>
{
InjectExtensions.SetServiceProvider(context.RequestServices);
await next();
});
The first call is remembered as the composition root and is never replaced. Later calls apply to the current async flow only, so concurrent requests cannot see each other's scope.
Lifetimes
The marker chooses the lifetime:
| Marker | Lifetime |
|---|---|
IAutoInjection |
scoped |
ITransientInjection |
transient |
ISingletonInjection |
singleton |
public interface IPriceCalculator : ITransientInjection { }
public interface IClock : ISingletonInjection { DateTime UtcNow { get; } }
A singleton must not depend on a scoped service such as a DbContext — the first scope's instance
would be captured and reused forever. Use RunInScopeAsync for that.
What gets registered against what
A class is registered against the marked interfaces it declares itself, not those it inherits from a base class. So given:
public interface IBankRepository : IRepository<Bank> { } // IRepository is marked
internal class BankRepository : Repository<Bank>, IBankRepository { }
BankRepository is registered as IBankRepository only. It does not also claim IRepository<Bank>
or the interfaces that composes — those belong to its base class, and every repository in the
application would otherwise compete to be the implementation of the same generic contracts.
Explicit registration
For pairs that do not match by convention, or where order matters:
[Injection(typeof(IEmailSender), typeof(SmtpEmailSender), order: 10)]
[Injection(typeof(INotifier), typeof(SmtpEmailSender), order: 20, lifetime: ServiceLifetime.Singleton)]
public class SmtpEmailSender : IEmailSender, INotifier { }
Order defaults to 999, lowest registers first, and the attribute is repeatable. Attributes are
applied after the marker pass, so one can deliberately override a convention registration.
If an attribute names an implementation that does not satisfy its interface, AddAutoInjection
throws at startup naming both types — rather than leaving you a resolution failure at first use.
Resolving without a constructor
Some types cannot take constructor arguments — a base class the container never sees, for example.
Inject<T>() resolves from the ambient provider:
public class BaseRepository
{
protected readonly IMapper _mapper;
protected readonly ICurrentUser _currentUser;
public BaseRepository()
{
_mapper = _mapper.InjectRequired();
_currentUser = _currentUser.InjectRequired();
}
}
It is an extension on the field being assigned, so the field's type supplies T and you never name
the type twice.
| Behaviour when not registered | |
|---|---|
InjectRequired<T>() |
throws, naming the missing type — prefer this |
Inject<T>() |
returns the field unchanged, normally null |
TryInject<T>(out T) |
returns false; for genuinely optional services |
if (default(IAuditLogger).TryInject(out var logger))
logger.Write(entry);
All three throw a clear error if SetServiceProvider was never called.
This is a service locator. It hides what a type depends on, makes it hard to test, and moves failures from startup to first use. Use constructor injection wherever it is available.
Scoped services in background work
A hosted service is a singleton, so resolving a DbContext in it would capture one instance for
the life of the process and share it across overlapping runs. Take a scope instead:
await InjectExtensions.RunInScopeAsync(async () =>
{
var repo = default(IUserRepository).InjectRequired();
await repo.SaveCreateAsync(user);
});
The scope is created from the composition root, is ambient for the duration, and is disposed
afterwards. The previous scope is restored, so this nests safely. A synchronous RunInScope exists
too.
IsConfigured tells you whether a provider has been set, for library code that must work either
way.
License
See LICENSE.txt. Free for personal and commercial use under the stated conditions.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net8.0
- Microsoft.Extensions.DependencyInjection (>= 8.0.0)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on Muslim.Inject:
| Package | Downloads |
|---|---|
|
Muslim.GenericRepository
EF Core repository for entities with audit columns: reads, writes, soft delete, bulk operations and raw SQL, with CreatedBy/UpdateBy/DeletedBy and UTC timestamps written automatically. Implements Muslim.GenericRepository.Interface. |
|
|
Muslim.Extensions
Small .NET extension helpers: AutoMapper Map<T>() via Muslim.Inject, empty/default checks, enum parsing, DateOnly conversion, null coalescing, and salted PBKDF2 password hashing with legacy SHA-256 migration. Includes build-delivered agent guidance. |
|
|
Muslim.Helpers
Small shared helpers for Muslim.* applications: the ICurrentUser contract (auto-registered through Muslim.Inject), a Base64 data URI decoder (Base64Data), and an EF Core comma-separated string list converter with a change-tracking comparer (StringListConverter, StringListComparer, HasStringListConversion). |
|
|
Muslim.GenericRepository.Interface
Repository abstraction over EF Core: IRepository<TEntity> and the interfaces it composes — reads, writes, soft delete, bulk operations, counts and raw SQL. Reference this to depend on the contract without the implementation. |
|
|
Muslim.GenericService.Interface
Service abstraction for Muslim.GenericService: IService<TEntity> and the interfaces it composes — reads, finds with paging, create, update, delete, soft delete and restore, counts and raw SQL — all returning Muslim.Result Result<T>. Reference this to depend on the contract without the implementation. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Adds AddAutoInjection(assemblies) so hosts no longer hand-write the reflection scan, plus ITransientInjection/ISingletonInjection markers, a Lifetime on [Injection], InjectRequired, TryInject and RunInScopeAsync. Inject() now reports a missing provider instead of silently returning null. Fixes a cross-request scope leak present through 2.0.0 — see CHANGELOG.md.