Oddhouse.Attic 1.0.1

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

Oddhouse.Attic

A dependency injection container with support for constructing objects from string representations. Useful when dependency graphs need to be configured through text — config files, databases, or user input — without writing registration code for each combination.


Basic Usage

Register types against the static root container and resolve them anywhere:

// Register
Attic.Register<ILogger, ConsoleLogger>();
Attic.Register<IDatabase, SqlDatabase>();
Attic.Register<IAppService, AppService>();

// Resolve — AppService is constructed with ILogger and IDatabase injected
var result = Attic.Resolve<IAppService>();
if (result.Success)
{
    result.Value.Run();
}

Constructor parameters are resolved automatically. AppService only needs to declare them:

public class AppService : IAppService
{
    public AppService(ILogger logger, IDatabase database) { ... }
}

Registration

Three ways to register a dependency:

// Map an interface to an implementation type (constructed via injection)
Attic.Register<IService, ServiceImpl>();

// Use a factory delegate
Attic.Register<IService>(() => new ServiceImpl(Environment.GetEnvironmentVariable("KEY")));

// Register a pre-existing instance
Attic.Register<IService>(myInstance);

The default scope is SingleInstance — one instance is created and reused. Pass an InstanceScope to change this:

Attic.Register<IService, ServiceImpl>(InstanceScope.Transient);

String-based Registration

Dependencies can also be described as strings and registered without writing factory code:

Attic.RegisterString<IService>("new MyApp.ServiceImpl('connection-string', timeout: 30)");

The string is parsed and validated at registration time. At resolution time the container constructs the object, injecting any container-registered dependencies via $:

// 'logger' is resolved from the container; the timeout is a literal value
Attic.RegisterString<IService>("new MyApp.ServiceImpl($, timeout: 30)");

Strings can come from any text source:

string value = configuration["Service"];
Attic.RegisterString<IService>(value);

See the DependencyString Format chapter at the end of this document for more information


Lifetime Scopes

A lifetime scope inherits all registrations from its parent container and can add its own. Disposing it disposes the instances it owns:

using var scope = Attic.BeginLifetimeScope();

// Register a per-request instance on the scope
scope.Register<ICurrentUser>(currentUser);

// Resolves AppService with ICurrentUser from the scope, everything else from the root
var result = scope.Resolve<IAppService>();

Use InstanceScope.SingleInstancePerLifetimeScope to give each scope its own instance of a type:

Attic.Register<IUnitOfWork, UnitOfWork>(InstanceScope.SingleInstancePerLifetimeScope);

Validation

Check the entire registration graph at startup to catch missing or misconfigured dependencies early:

var result = Attic.CanCreateRegisteredTypes();
if (!result.Success)
    throw new Exception($"Container misconfigured: {result.Error}");

You can also check a specific type or string without resolving it:

OperationResult canResolve = Attic.CanResolve<IService>();
OperationResult canCreate  = Attic.CanCreateWithInjection<ServiceImpl>();
OperationResult canBuild   = Attic.CanCreateFromString<IService>("new MyApp.ServiceImpl()");

OperationResult

All methods return OperationResult or OperationResult<T> instead of throwing on failure:

var result = Attic.Resolve<IService>();

if (result.Success)
    Use(result.Value);
else
    Log(result.Error);

// Implicit bool conversion works too
if (Attic.CanResolve<IService>())
    ...

Multiple Containers

Attic.Root is a convenience for the common single-container case. For more control, create independent containers directly:

IContainer container = Attic.CreateContainer();
container.Register<IService, ServiceImpl>();

ILifetimeScope scope = container.BeginLifetimeScope();

DependencyString Format

A DependencyString is a textual representation of a value or object that the container can construct. It is parsed from a plain string and supports primitives, object construction, collection initializers, property initializers, and dependency injection.

Whitespace between tokens is always ignored.


Primitives

Boolean

true
false

Assigns to any bool or bool? target.

Integer

42
-7

Parsed as long. Assignable to byte, sbyte, short, ushort, int, uint, long, ulong, decimal, float, double, and any enum whose underlying type is an integer type.

Decimal

3.14
-0.5

Parsed as decimal. Assignable to decimal, float, and double.

String

'hello world'
"hello world"

Both single and double quotes are supported. Use \ to escape any character, including the quote character itself.

'it\'s a string'
"say \"hello\""

A single-character string is also assignable to char. String values can additionally be assigned to any type that has a TypeConverter capable of converting from string (e.g. Guid, Uri).

DateTime

Dates are parsed using CultureInfo.InvariantCulture. The following formats are supported:

Format Kind
2025-01-31 Local
2025-01-31 14:30 Local
2025-01-31 14:30:00 Local
2025-01-31 14:30:00.123456 Local
2025-01-31T14:30:00+01:00 Local with offset
2025-01-31T14:30:00.123456+01:00 Local with offset
2025-01-31T14:30:00Z UTC
2025-01-31T14:30:00.123456Z UTC

Assignable to DateTime and DateTimeOffset.

TimeSpan

1.02:03:04
00:30:00

Parsed using the standard .NET TimeSpan.Parse format with CultureInfo.InvariantCulture. Assignable to TimeSpan.

Null

null

Assignable to any reference type or nullable value type (T?).


Dependency Injection

$

Resolves the value from the container. The type to resolve is inferred from context (e.g. the constructor parameter type or registered dependency type). This cannot be used when creating a DependencyString outside of a container context.


Object Construction

Basic construction

TypeName(arg1, arg2)
new TypeName(arg1, arg2)

The new keyword is optional when arguments are present; both forms behave identically. Each argument is itself a DependencyString and follows the same format rules recursively.

Omitting the type name

When the target type can be inferred from context (e.g. a constructor parameter type), the type name may be omitted:

(arg1, arg2)
new (arg1, arg2)

Named arguments

Arguments can be named using name: value syntax:

TypeName(width: 800, height: 600)

Positional arguments must come before named arguments, matching the same rules as C#.

Type-only reference (no new)

A bare type name without parentheses or braces resolves the value from the container. If no registration is found, constructor injection is attempted:

TypeName

Collection initializer

new TypeName { element1, element2, element3 }

Elements are added by calling the type's public Add method after construction. The element type is inferred from the type's IEnumerable<T> interface. Can be combined with constructor arguments:

new TypeName(arg1) { element1, element2 }

Property initializer

Named entries in { } set public writable properties after construction:

new TypeName { PropertyA = value1, PropertyB = value2 }

Combined

Constructor arguments, properties, and collection elements can all be combined. Properties and elements are distinguished by whether a name is present:

new TypeName(arg1) { element1, element2, PropertyA = value1 }

Type Names

Built-in keywords

The C# primitive type keywords are supported: bool, byte, sbyte, char, decimal, double, float, int, uint, long, ulong, short, ushort, string, object.

Additionally, List and Dictionary can be used as shorthand for System.Collections.Generic.List<T> and System.Collections.Generic.Dictionary<TKey, TValue>.

Fully qualified names

System.DateTime()
System.Text.StringBuilder()

Nested types use + as the separator (or . is also accepted by the parser):

Outer+Inner()

With assembly name

An assembly name can be appended after a comma:

My.Namespace.MyType, MyAssembly

Alternatively, the type can be wrapped in square brackets to disambiguate in nested positions:

[My.Namespace.MyType, MyAssembly]()

Generic types

Generic arguments are specified with <>:

new List<string>()
new Dictionary<string, int>()

In nested positions (e.g. a generic argument that itself has an assembly name), wrap the inner type in square brackets:

new [System.Collections.Generic.List<[System.Int32, mscorlib]>, mscorlib]()

Arrays

Append [] to any type name:

new string[] { 'a', 'b', 'c' }
new int[][] { }

When the element type can be inferred from context, the type name can be omitted:

new [] { 'a', 'b', 'c' }

Full Examples

// Primitives
42
3.14
true
'hello'
2025-06-01
00:15:00
null

// Simple construction
new System.Text.StringBuilder()
new System.Text.StringBuilder(64)

// Named arguments
new System.Text.StringBuilder(capacity: 64)

// Resolve from container
MyApp.Services.ILogger

// Generic collection
new List<string> { 'one', 'two', 'three' }

// Array
new string[] { 'one', 'two', 'three' }

// Constructor + properties + elements
new MyApp.Config(true) { Timeout = 30, 'item1', 'item2' }

// Assembly-qualified type
[MyApp.Services.Logger, MyApp]()

// Inject a dependency from the container as a constructor argument
new MyApp.Service($, 'config-value')
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

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
1.0.1 120 5/22/2026
1.0.0 106 5/17/2026

Support for open generics