IoC Container & Auto Configuration

TimeLine IoC, Auto Configuration and IoC attributes documentation

This page explains how the TimeLine IoC container works, how to mark classes with IoC attributes, and how to work with automatically exported IoC configuration.

It is meant for developers and consultants who use the Framework and need to register, extend, or resolve IoC-based components.

What is IoC?

The abbreviation is "Inversion of Control", and basically means that objects will be resolved as the correct type automatically, you do not have to think about it. Only know what you need and ask the container for that type.


1. Core Concepts

1.1 Container & Auto Configuration

  • The global container is TimeLine.Framework.Util.IocContainer.Instance.

  • Auto configuration is handled by IocAutoConfiguration, accessible via:

    var autoConfig = IocContainer.Instance.AutoConfiguration;
  • At build time, a JSON config is generated per assembly. At runtime, IocAutoConfiguration.Load() reads these files and wires types to the container.

  • AutoConfiguration usually does not have to be called manually.

You usually don't interact with the JSON directly. You:

  • Mark types with IoC attributes →

  • IocConfigExporter picks them up →

  • IocAutoConfiguration wires them into TinyIoC.


2. IoC Attributes

All IoC attributes inherit from IocBaseAttribute. Apply them to classes or interfaces to control how they are registered and used.

2.1 Overview

Attribute
Target
Purpose

IocObject

class / interface

Mark type as IoC-managed object (base of most scenarios).

IocExplictImplementationFor

class

Explicit implementation for given interface/abstract base.

IocTag

class / interface

Categorization/tagging for lookup and grouping.

IocRunStatic

class / interface

Run static constructor at startup (without registering type as IoC object).

IocIndependentObject

class

Derived class that should not replace its base in the IoC inheritance chain.

IocDerivedObjectsOnly

class / interface

Base/template type: only concrete children become IoC objects, base itself isn't used.


2.2 IocObjectAttribute

Marks a type as an IoC object.

Key behavior

  • For non-abstract classes:

    • Auto-config will register the type (or its most derived replacement) in the container.

  • For abstract classes / interfaces:

    • Used as base / template; concrete derived types will be picked up.

  • The attribute will automatically be inherited, and does not have to be applied to derived types. (But can, for clarity)

Properties

  • bool RunStatic If true, the static constructor is forced at startup.

  • string Reason Optional explanation for docs/logging.

Example


2.3 IocExplictImplementationForAttribute

Marks a concrete class as the explicit implementation for an interface or abstract base.

Key behavior

  • The container will resolve the interface/abstract type to this class.

  • Also implies IoC registration (similar to IocObject).

  • This can be applied to interfaces/types you do not have code access to, e.g. third-party types.

Example


2.4 IocTagAttribute

Tags a type for later lookup.

Key behavior

  • Tags show up in auto config and can be queried via GetTagged.

  • Inherited by derived classes if applied on bases.

Example

Look up all tagged types:


2.5 IocRunStaticAttribute

Triggers static construction without registering an IoC object.

Key behavior

  • Use it for static-only classes or global setup that runs during IoC auto-configuration.

  • This setup will run even though the type is not accessed/resolved anywhere in code up to that point.

  • It will automatically load the DLL if it hasn't been loaded yet.

  • Does not automatically register the type for Create<T>.

Example

If you also want the type as an IoC object, use [IocObject(RunStatic = true) instead.


2.6 IocIndependentObjectAttribute

Marks a derived class as an independent IoC root.

Problem it solves

Normally, the auto config treats inheritance chains as "replacement chains". The most derived type replaces the base in IoC registration. That's great for customizations, but sometimes a derived type should be completely separate, not a replacement.

Behavior

  • Applied to a non-abstract class.

  • Auto config will:

    • Register the class as its own IoC object.

    • Not chain it as a replacement of its base.

    • No inheritance-based "replaced X with Y" warnings.

    • Derived children of this type will be registered as replacements to this type.

Example

Result:

  • ImportHandlerBase → DefaultImportHandler replacement chain.

  • DiagnosticsImportHandler is its own IoC root, not part of that chain.


2.7 IocDerivedObjectsOnlyAttribute

Marks a base type where only the derived concrete types are IoC objects.

Problem it solves

Sometimes a base type is just a template, and every concrete subclass should be its own IoC object, not "the latest replacement for the base".

Behavior

  • Applied to a base class or interface.

  • Auto config will:

    • Not register the base itself in IoC.

    • Treat each direct concrete subclass as its own IoC root (no replacement chain starting from the base).

    • Customizations of those subclasses still use normal replacement logic.

Example

Result:

  • ExportJobBase → not registered in IoC.

  • CustomerExportJob → own IoC root.

  • InvoiceExportJob → own IoC root.

A further customization:

Now CustomerExportJob has its own replacement chain:

  • CustomerExportJob → ProjectXCustomerExportJob, without affecting InvoiceExportJob.


3. Creating Instances via IocContainer

3.1 Basic creation

Use IocContainer.Instance.Create<T> to create IoC-managed objects.

This will:

  • Ensure the type is registered via auto configuration (if it was exported as such).

  • Resolve it from the underlying TinyIoCContainer.

There are also TryCreate variants:

3.2 Singleton pattern with IoC

Typical pattern when you want a singleton that's IoC-aware:

  • Mark the type with [IocObject].

  • Use a static Instance property that resolves via IocContainer.

  • Keep the constructor protected (or internal) to discourage manual new.

3.2.1 Base class 'Singleton'

There is a shared base class that can be used for the singleton pattern in TimeLine.Client.Framework, called TimeLine.Client.Framework.Singleton<T> that can be used and implements the singleton pattern and [IocObject] tag automatically.


4. IoC Objects with Constructor Arguments

Sometimes you need constructor arguments that can't be auto-resolved.

The container exposes overloads that take named parameters:

All of these end up as NamedParameters under the hood and are forwarded to TinyIoC. Usage of the first or third variant is recommended, depending on preference.

Arguments will be passed in right into the constructor, no init step is required. This allows even get-only properties to be set correctly in an IoC object.


5. Working with Auto Configuration

Sometimes you need to inspect or work directly with the IoC metadata.

5.1 Accessing the auto configuration

The key parts:

  • Dictionary<string, Entry> Types All known IoC entries, keyed by fully qualified type name.

  • Dictionary<string, HashSet<Entry>> Tags All tagged entries.

  • Entry this[Type type] Indexer by System.Type.

  • Entry this[string typeName] Indexer by fully qualified name.

5.2 Getting all entries for a tag

GetTagged:

  • Cleans up inheritance chains (returns the most derived applicable entries).

  • Can filter by onlyRegistered if needed.

5.3 Inspecting inheritance / derived types

Each Entry exposes:

  • Entry Base The base entry (config-level, not necessarily the CLR BaseType).

  • Entry Derived The configured replacement / derived entry.

  • Entry MostDerived The last replacement in the chain.

  • bool? Register Whether this entry is configured to be registered.

  • bool IsRegistered Whether the container registration actually happened.

  • Type LoadType() Loads the actual CLR type for the entry.

  • object CreateInstance() Convenience method to create an instance of the most derived implementation.

Example: find the effective implementation for a base type:

Special attributes:

  • IocIndependentObject types will not appear as Derived of their base.

  • IocDerivedObjectsOnly bases will have Register != true, and their direct children will not have Extends pointing to that base.


6. Guidelines & Best Practices

  • Use [IocObject] for:

    • Services, helpers, converters, behaviors, import/export handlers, etc.

    • Any class that is intended to be customizable in customizations.

    • Anything that should be resolved through the framework container instead of new.

  • Use [IocExplictImplementationFor] when:

    • There is exactly one default implementation for an interface/abstract base.

    • You do not have code access to the base type.

  • Use [IocTag] to:

    • Group related components (controllers, jobs, strategies) and load them dynamically.

  • Use [IocRunStatic] or IocObject.RunStatic = true for:

    • Types that need static setup during startup.

  • Use [IocIndependentObject] when:

    • A derived class must not override/replace the base in IoC.

  • Use [IocDerivedObjectsOnly] when:

    • You have a base template and want each concrete child to be its own independent IoC object.

  • When in doubt:

    • Resolve types via IocContainer.Instance.Create<T>() or TryCreate. The container will even resolve if the type was not registered as an IoC object.

    • Avoid new on IoC types at all costs, only create them via the IocContainer.

This is enough for using IoC right in day-to-day work. For anything more exotic, inspect IocAutoConfiguration and the generated JSON, but for most use cases the attributes and the container APIs are all that is needed.

Last updated