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 →
IocConfigExporterpicks them up →IocAutoConfigurationwires them intoTinyIoC.
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
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
IocObjectAttributeMarks 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 RunStaticIftrue, the static constructor is forced at startup.string ReasonOptional explanation for docs/logging.
Example
[IocObject(Reason = "General-purpose helper that should be resolved via IoC.")]
public class SomeHelper
{
public SomeHelper() { }
public void DoStuff() { ... }
}2.3 IocExplictImplementationForAttribute
IocExplictImplementationForAttributeMarks 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
public interface IMyService
{
void Run();
}
[IocExplictImplementationFor(typeof(IMyService), Reason = "Default implementation used by the framework.")]
public class DefaultMyService : IMyService
{
public void Run() { ... }
}2.4 IocTagAttribute
IocTagAttributeTags 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
[IocObject]
[IocTag("ApiController", Reason = "Used to find all API controllers.")]
public class TimeLineApiController
{
...
}
public class MyApiController : TimeLineApiController
{
...
}Look up all tagged types:
var taggedControllers = IocContainer.Instance
.AutoConfiguration
.GetTagged("ApiController");2.5 IocRunStaticAttribute
IocRunStaticAttributeTriggers 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
[IocRunStatic(Reason = "Register global converters.")]
public static class GlobalConversionSetup
{
static GlobalConversionSetup()
{
// This runs once when IoC auto-run executes.
}
}If you also want the type as an IoC object, use [IocObject(RunStatic = true) instead.
2.6 IocIndependentObjectAttribute
IocIndependentObjectAttributeMarks 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
[IocObject]
public abstract class ImportHandlerBase
{
...
}
[IocObject(Reason = "Default import handler.")]
public class DefaultImportHandler : ImportHandlerBase
{
...
}
// Independent, not a replacement:
[IocObject(Reason = "Diagnostics-only handler.")]
[IocIndependentObject(Reason = "Should not replace ImportHandlerBase chain.")]
public class DiagnosticsImportHandler : ImportHandlerBase
{
...
}Result:
ImportHandlerBase→DefaultImportHandlerreplacement chain.DiagnosticsImportHandleris its own IoC root, not part of that chain.
2.7 IocDerivedObjectsOnlyAttribute
IocDerivedObjectsOnlyAttributeMarks 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
[IocObject]
[IocDerivedObjectsOnly(Reason = "Abstract base for export jobs; never resolved directly.")]
public abstract class ExportJobBase
{
...
}
[IocObject]
public class CustomerExportJob : ExportJobBase
{
...
}
[IocObject]
public class InvoiceExportJob : ExportJobBase
{
...
}Result:
ExportJobBase→ not registered in IoC.CustomerExportJob→ own IoC root.InvoiceExportJob→ own IoC root.
A further customization:
[IocObject(Reason = "Project-specific customization of customer export.")]
public class ProjectXCustomerExportJob : CustomerExportJob
{
...
}Now CustomerExportJob has its own replacement chain:
CustomerExportJob→ProjectXCustomerExportJob, without affectingInvoiceExportJob.
3. Creating Instances via IocContainer
IocContainer3.1 Basic creation
Use IocContainer.Instance.Create<T> to create IoC-managed objects.
var helper = IocContainer.Instance.Create<SomeHelper>();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:
if (IocContainer.Instance.TryCreate(out SomeHelper helper))
{
// use helper
}3.2 Singleton pattern with IoC
Typical pattern when you want a singleton that's IoC-aware:
[IocObject]
public class SomeService
{
private static SomeService _instance;
public static SomeService Instance =>
_instance ?? (_instance = IocContainer.Instance.Create<SomeService>());
protected SomeService() { }
// members...
}Mark the type with
[IocObject].Use a static
Instanceproperty that resolves viaIocContainer.Keep the constructor
protected(orinternal) to discourage manualnew.
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:
// params NamedParameter[]
var obj = IocContainer.Instance.Create<MyType>(
new NamedParameter("customerId", 123),
new NamedParameter("mode", ExportMode.Full)
);
// params KeyValuePair<string, object>[]
var obj2 = IocContainer.Instance.Create<MyType>(
new KeyValuePair<string, object>("customerId", 123),
new KeyValuePair<string, object>("mode", ExportMode.Full)
);
// params (string name, object value)[]
var obj3 = IocContainer.Instance.Create<MyType>(
("customerId", 123),
("mode", ExportMode.Full)
);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
var autoConfig = IocContainer.Instance.AutoConfiguration;The key parts:
Dictionary<string, Entry> TypesAll known IoC entries, keyed by fully qualified type name.Dictionary<string, HashSet<Entry>> TagsAll tagged entries.Entry this[Type type]Indexer bySystem.Type.Entry this[string typeName]Indexer by fully qualified name.
5.2 Getting all entries for a tag
var apiControllers = autoConfig.GetTagged("ApiController");
// Example: create all registered controllers
foreach (var entry in apiControllers)
{
if (!entry.IsRegistered)
continue;
var instance = entry.CreateInstance(); // calls AssureRegistration + resolve
// instance is object; cast as needed
}GetTagged:
Cleans up inheritance chains (returns the most derived applicable entries).
Can filter by
onlyRegisteredif needed.
5.3 Inspecting inheritance / derived types
Each Entry exposes:
Entry BaseThe base entry (config-level, not necessarily the CLRBaseType).Entry DerivedThe configured replacement / derived entry.Entry MostDerivedThe last replacement in the chain.bool? RegisterWhether this entry is configured to be registered.bool IsRegisteredWhether 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:
var baseEntry = autoConfig[typeof(ExportJobBase)];
var effectiveEntry = baseEntry?.MostDerived;
if (effectiveEntry?.IsRegistered == true)
{
var jobInstance = effectiveEntry.CreateInstance(); // object
}Special attributes:
IocIndependentObjecttypes will not appear asDerivedof their base.IocDerivedObjectsOnlybases will haveRegister != true, and their direct children will not haveExtendspointing 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]orIocObject.RunStatic = truefor: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>()orTryCreate. The container will even resolve if the type was not registered as an IoC object.Avoid
newon 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