Overview
Silky uses Autofac as its underlying IoC container. By injecting AutofacServiceProviderFactory into the .NET host, Silky replaces the default IServiceCollection container with Autofac's more powerful DI capabilities.
In addition to the standard IServiceCollection.AddXxx() or explicit Autofac ContainerBuilder registrations, Silky provides a convention-based dependency injection mechanism: implement a marker interface and the framework automatically scans and registers your class at startup — no manual registration code required.
Marker Interfaces
Three marker interfaces correspond to Autofac's three lifecycle scopes:
| Interface | Autofac Lifetime | Description |
|---|---|---|
ITransientDependency | InstancePerDependency | Transient: a new instance per resolution |
IScopedDependency | InstancePerLifetimeScope | Scoped: one instance per lifetime scope (per request) |
ISingletonDependency | SingleInstance | Singleton: one instance for the entire application lifetime |
Usage
// Transient: new instance every time
public class OrderService : IOrderService, ITransientDependency
{
}
// Scoped: same instance within one request scope
public class CurrentUserContext : ICurrentUserContext, IScopedDependency
{
}
// Singleton: same instance throughout the application
public class LocalServiceEntryManager : IServiceEntryManager, ISingletonDependency
{
}
Auto-Registration Logic (DefaultDependencyRegistrar)
public class DefaultDependencyRegistrar : IDependencyRegistrar
{
public void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
var refAssemblies = typeFinder.GetAssemblies();
foreach (var assembly in refAssemblies)
{
// Singleton
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(ISingletonDependency).IsAssignableFrom(t)
&& !t.GetCustomAttributes().OfType<InjectNamedAttribute>().Any())
.PropertiesAutowired()
.AsImplementedInterfaces()
.AsSelf()
.SingleInstance();
// Transient
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(ITransientDependency).IsAssignableFrom(t)
&& !t.GetCustomAttributes().OfType<InjectNamedAttribute>().Any())
.PropertiesAutowired()
.AsImplementedInterfaces()
.AsSelf()
.InstancePerDependency();
// Scoped
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(IScopedDependency).IsAssignableFrom(t)
&& !t.GetCustomAttributes().OfType<InjectNamedAttribute>().Any())
.PropertiesAutowired()
.AsImplementedInterfaces()
.AsSelf()
.InstancePerLifetimeScope();
}
}
}
Key points:
- All convention-registered types have
PropertiesAutowired()enabled — public properties are also injected by Autofac - Types marked with
[InjectNamed]are excluded from this scan — handled separately byNamedServiceDependencyRegistrar
Property Injection (PropertiesAutowired)
Convention-registered classes can receive dependencies via public property injection, without listing them in the constructor:
public class DefaultServerMessageReceivedHandler : ISingletonDependency
{
// Property injection: Autofac sets this automatically
public ILogger<DefaultServerMessageReceivedHandler> Logger { get; set; }
// Constructor injection still works alongside property injection
private readonly IServiceEntryLocator _serviceEntryLocator;
public DefaultServerMessageReceivedHandler(IServiceEntryLocator serviceEntryLocator)
{
_serviceEntryLocator = serviceEntryLocator;
}
}
Named Injection ([InjectNamed])
When multiple implementations of the same interface exist, use [InjectNamed] with a named key to differentiate them:
[InjectNamed("Zookeeper")]
public class ZookeeperRegistryCenter : IRegistryCenter, ISingletonDependency
{
}
[InjectNamed("Consul")]
public class ConsulRegistryCenter : IRegistryCenter, ISingletonDependency
{
}
Resolve by name:
var center = EngineContext.Current.ResolveNamed<IRegistryCenter>("Zookeeper");
Manual Resolution (EngineContext.Current)
For cases where constructor injection is unavailable (e.g., static helpers, factory classes):
// Resolve by type
var executor = EngineContext.Current.Resolve<IExecutor>();
// Resolve named service
var registryCenter = EngineContext.Current.ResolveNamed<IRegistryCenter>("Consul");
// Access the host name
var hostName = EngineContext.Current.HostName;
Warning
Manual resolution via EngineContext.Current bypasses DI lifetime management. Prefer constructor or property injection wherever possible.
