Scopist 0.2.1

dotnet add package Scopist --version 0.2.1
                    
NuGet\Install-Package Scopist -Version 0.2.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="Scopist" Version="0.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Scopist" Version="0.2.1" />
                    
Directory.Packages.props
<PackageReference Include="Scopist" />
                    
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 Scopist --version 0.2.1
                    
#r "nuget: Scopist, 0.2.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 Scopist@0.2.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=Scopist&version=0.2.1
                    
Install as a Cake Addin
#tool nuget:?package=Scopist&version=0.2.1
                    
Install as a Cake Tool

Scopist

Make resolving scoped services safe, simple, and explicit.

Scopist gives you a tiny abstraction IScopedResolver<T> so you can resolve a scoped service from an IServiceScope in places where constructor-injection isn’t available (background jobs, timers, event handlers, etc.).

It also ships with a startup validator (ScopistChecker) that ensures every IScopedResolver<T> you use targets a service that is actually registered as Scoped — preventing subtle lifetime bugs at runtime.

Why you’ll love it:

  • Minimal API surface, zero magic.
  • Clear intent: “I need T from this scope.”
  • Fast feedback: validation at startup or on-demand.
  • Works with the standard Microsoft.Extensions.DependencyInjection stack.

Install

dotnet add package Scopist

TL;DR (60 seconds)

  1. Register Scopist:
var services = new ServiceCollection()
    .AddScopist(); // includes startup validation

services.AddScoped<MyScopedService>();
services.AddSingleton<MyWorker>();
  1. Inject IScopedResolver<T> where you need it, and resolve within a scope:
public sealed class MyWorker(IScopedResolver<MyScopedService> scoped, IServiceScopeFactory scopes)
{
    public async Task RunAsync()
    {
        using var scope = scopes.CreateScope();
        var s = scoped.Resolve(scope);
        await s.DoWorkAsync();
    }
}

That’s it. If you mistakenly register MyScopedService as Singleton/Transient, Scopist will fail fast with a clear error.


The problem Scopist solves

In real apps, not everything can be constructor-injected. Background tasks, queues, timers, and some event-driven flows need to create a scope and resolve services later.

Common pitfalls without Scopist:

  • Grabbing IServiceProvider everywhere and calling GetRequiredService<T>() (hard to track, easy to misuse).
  • Accidentally resolving a scoped dependency from a root provider (lifetime bugs, memory leaks, data corruption).

Scopist fixes this by:

  • Making scope usage explicit: IScopedResolver<T>.Resolve(IServiceScope scope).
  • Validating that every T you resolve is actually registered as Scoped.

Quick start

1) Register

var services = new ServiceCollection()
    .AddScopist(); // Registers IScopedResolver<T> and the startup checker

services.AddScoped<MyScopedService>();
services.AddSingleton<MyBackgroundJob>();

var provider = services.BuildAndValidateServiceProvider();

2) Use it safely

public sealed class MyBackgroundJob(IScopedResolver<MyScopedService> scoped, IServiceScopeFactory scopes)
{
    public int Execute()
    {
        using var scope = scopes.CreateScope();
        var svc = scoped.Resolve(scope);
        return svc.GetValue();
    }
}

public sealed class MyScopedService
{
    public int GetValue() => 42;
}

Validation (ScopistChecker)

Scopist validates two ways:

  • Startup validation (default): when you call services.AddScopist(), Scopist wires a checker that runs on provider activation. If an IScopedResolver<T> is used but T isn’t registered as Scoped, the app fails fast with a detailed message.

  • Manual validation (on-demand): after building the provider, you can call provider.ValidateScopist() to run the same checks when it suits you (useful for tests or custom bootstraps).

var services = new ServiceCollection()
    .AddScopist();

// OOPS: Wrong lifetime below — this will fail validation
services.AddSingleton<MyScopedService>();

var provider = services.BuildAndValidateServiceProvider();
// Throws ScopistValidationException: "Service MyScopedService must be registered as Scoped..."

Example: Manual validation

var services = new ServiceCollection()
    .AddScopist();

services.AddSingleton<MyScopedService>();

var provider = services.BuildServiceProvider();
provider.ValidateScopist(); // Throws ScopistValidationException if misconfigured

Error type: ScopistValidationException (inherits InvalidOperationException) with all messages aggregated.


Design notes

  • IScopedResolver<T> is intentionally tiny. It only resolves within a provided IServiceScope.
  • The checker reflects over your registered services, inspects constructors for IScopedResolver<T>, and verifies T has a Scoped registration.
  • No runtime proxies; no container replacement. Works with Microsoft.Extensions.DependencyInjection.

FAQ

Can I still use constructor injection? Yes — Scopist is for the cases where you can’t (or shouldn’t) inject a scoped service directly.

What if I need multiple scoped services? Inject multiple IScopedResolver<T>s and resolve each within the same IServiceScope.

Does it create instances eagerly? No. Resolution happens only when you call Resolve(scope).

ASP.NET Core compatible? Yes. Use in hosted services, background jobs, minimal APIs, etc.


License

MIT

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

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
0.2.1 241 10/5/2025
0.2.0 214 10/5/2025
0.1.2 164 9/27/2025