Skip to content

Dependency Injection

Gustavo Viana edited this page Aug 7, 2026 · 5 revisions

Dependency Injection

FileHub.DependencyInjection adds IServiceCollection helpers for registering any hub — Local, Memory, OCI, or custom. No driver assembly is referenced from it, so you pay only for what you use.

dotnet add package FileHub.DependencyInjection

Single hub

using FileHub.DependencyInjection;
using FileHub.Local;

services.AddFileHub<ILocalFileHub>(sp => new LocalFileHub(@"C:\data"));

Registers two services pointing at the same instance: ILocalFileHub and IFileHub. Inject either.

Overloads

Overload Registers Use
AddFileHub<T>(Func<IServiceProvider, T>, ServiceLifetime = Singleton) T + IFileHub Driver-typed injection (e.g. ILocalFileHub). Default singleton.
AddFileHub<T>(T instance) T + IFileHub (singleton) Prebuilt instance.
AddFileHub(Func<IServiceProvider, IFileHub>, ServiceLifetime = Singleton) IFileHub only Driver-agnostic consumer code.
AddFileHub(IFileHub instance) IFileHub (singleton) Prebuilt instance, no typed slot.
// Scoped factory
services.AddFileHub<ILocalFileHub>(
    sp => new LocalFileHub(@"C:\data"),
    ServiceLifetime.Scoped);

// Prebuilt instance (always singleton)
services.AddFileHub<IMemoryFileHub>(new MemoryFileHub());

// Per-tenant single hub
services.AddFileHub(sp =>
{
    var tenant = sp.GetRequiredService<ITenantContext>().Id;
    return new LocalFileHub($@"C:\tenants\{tenant}");
}, ServiceLifetime.Scoped);

Cloud drivers (OCI) should usually be singleton so the HTTP client is reused.

Named hubs

For multiple hubs, use AddNamedFileHubs. The callback receives a NamedFileHubsServiceBuilder; the extension wires up an INamedFileHubs registry (scoped) that you inject.

services.AddNamedFileHubs(b => b
    .Register("reports", new MemoryFileHub())
    .Register("logs",    new LocalFileHub(@"C:\logs")));

Register has two forms:

Form Lifetime Use
Register(name, IFileHub instance) Singleton Prebuilt instance.
Register(name, Func<IServiceProvider, IFileHub>, ServiceLifetime = Singleton) Configurable Factory that sees the scope provider — supports tenant scoping.

Tenant example

services.AddScoped<ITenantContext, HttpTenantContext>();

services.AddNamedFileHubs(b => b.Register(
    "tenant",
    sp => new LocalFileHub($@"C:\tenants\{sp.GetRequiredService<ITenantContext>().Id}"),
    ServiceLifetime.Scoped));

Each request scope gets its own hub; within a scope, GetByName("tenant") returns the same cached instance.

Using the registry

public class UploadsController(INamedFileHubs hubs)
{
    public IActionResult Put(string name)
    {
        var hub = hubs.GetByName("tenant")!;
        hub.Root.CreateFile(name);
        return Ok();
    }
}
  • GetByName(name) → IFileHub or null (never throws; case-insensitive).
  • GetRootByName(name) → the root DirectoryEntry or null.

Startup-time configuration

When setup itself needs DI (e.g. IOptions<T>), the (sp, builder) => … overload exposes the root provider:

services.AddNamedFileHubs((sp, b) =>
{
    var opts = sp.GetRequiredService<IOptions<StorageOptions>>().Value;

    b.Register("reports", new LocalFileHub(opts.ReportsPath));

    b.Register(
        "tenant",
        innerSp => new LocalFileHub(Path.Combine(
            opts.TenantsRoot,
            innerSp.GetRequiredService<ITenantContext>().Id)),
        ServiceLifetime.Scoped);
});

The outer sp runs once (singleton spec build); innerSp runs per lookup.

Behaviour

  • Registry is scoped. Inject INamedFileHubs into scoped/transient consumers. Singletons that hold it capture one scope — don't.
  • Per-entry lifetime honoured. Singleton hubs live at root; scoped hubs are cached per scope on the registry; transient hubs are rebuilt on every GetByName.
  • Disposal. Scoped hubs dispose with their scope; singleton hubs dispose with the root container; transient hubs are not tracked (callers own disposal).
  • Immutable after AddNamedFileHubs returns. No mutation methods on INamedFileHubs.
  • Not keyed DI. provider.GetRequiredKeyedService<IFileHub>("reports") throws — access is only through INamedFileHubs.

Standalone (no DI)

INamedFileHubs hubs = new NamedFileHubsBuilder()
    .Register("reports", new MemoryFileHub())
    .Register("logs",    new LocalFileHub(@"C:\logs"))
    .Build();

The core NamedFileHubsBuilder supports prebuilt instances only — no factories/lifetimes. Use AddNamedFileHubs for those.

Integration-test swap

if (builder.Environment.IsEnvironment("IntegrationTest"))
    builder.Services.AddFileHub(_ => (IFileHub)new MemoryFileHub());
else
    builder.Services.AddFileHub(_ => (IFileHub)new LocalFileHub(@"C:\data"));

Consumer code depending on IFileHub doesn't change. More at Testing.

Clone this wiki locally