Muslim.Inject 8.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Muslim.Inject --version 8.0.0
                    
NuGet\Install-Package Muslim.Inject -Version 8.0.0
                    
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="Muslim.Inject" Version="8.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Muslim.Inject" Version="8.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Muslim.Inject" />
                    
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 Muslim.Inject --version 8.0.0
                    
#r "nuget: Muslim.Inject, 8.0.0"
                    
#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 Muslim.Inject@8.0.0
                    
#: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=Muslim.Inject&version=8.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Muslim.Inject&version=8.0.0
                    
Install as a Cake Tool

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, not services.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.

For a local rather than a field — nothing to extend — each has a parameterless form instead:

var userRepository = InjectExtensions.InjectRequired<IUserRepository>();
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);

// or, with no existing value to extend:
if (InjectExtensions.TryInject<IAuditLogger>(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.

Resetting between tests

SetServiceProvider latches the composition root on its first-ever call only, and ignores every call after that — the point being that a per-request scope can never overwrite it in production. In a test process the same latch means every test after the first keeps resolving root-level work against the first test's provider, even once it is disposed. Reset() clears both the root and the current scope, so the next test's SetServiceProvider call latches its own root:

public void Dispose() => InjectExtensions.Reset();

The root it clears is a plain static, not an AsyncLocal, so this is not safe under parallel test execution — one test's Reset() can pull the root out from under another test still running. Use it only when tests run sequentially (for example, an xUnit collection with parallelization disabled).

License

See LICENSE.txt. Free for personal and commercial use under the stated conditions.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
10.0.1 166 10/15/2026
8.0.0 255 9/23/2026
2.3.0 109 9/23/2026
2.2.0 249 8/10/2026
2.0.0 566 10/5/2024

Adds parameterless Inject{T}()/InjectRequired{T}()/TryInject{T}(out T) overloads for resolving into a new local rather than an existing field, and Reset() to clear the composition root and ambient scope between tests. See CHANGELOG.md.