I saw an increasing demand for mini workflows / domain sub-parts in one of my projects. Most containers have some kind of support for sub scopes or nested containers, but I do not want to expose the Container API. If you have few places were you need nested containers you can implement this directly for your container of choice. An example of this is a container specific implementation of Web API’s IDependencyResolver. But in our case we had several different needs for scoped contexts near the domain. My solution was to abstract the context in a interface IScopedContext.
public interface IScopedContext : IDisposable { } public interface IScopedContext<out TEntryPoint> : IScopedContext where TEntryPoint : class { TEntryPoint EntryPoint { get; } }
My StructureMap implementation
internal sealed class StructureMapScopedContext<TEntryPoint> : IScopedContext<TEntryPoint> where TEntryPoint : class { private readonly IContainer nested; public StructureMapScopedContext(IContainer container) { nested = container.GetNestedContainer(); nested.Configure(config => config.For<IScopedContext>().Use(this)); EntryPoint = nested.GetInstance<TEntryPoint>(); } public TEntryPoint EntryPoint { get; private set; } public void Dispose() { nested.Dispose(); } }
Minimal usage of the interface could be
public class Host { private readonly IScopedContext<MyWorker> myWorkerContext; public Host(IScopedContext<MyWorker> myWorkerContext) { myWorkerContext = myWorkerContext; } public void Start() { using(myWorkerContext) { myWorkerContext.EntryPoint.Start(); } } }
All transient registrations will live during the scope (This is specific for Structuremap). Here is a real life implementation from one of our Caliburn.Micro enabled systems.
public class ShowDialogResult<TModel> : Result where TModel : Screen { private readonly IWindowManager windowManager; private readonly IScopedContext<TModel> scope; private Action<TModel> configure; public ShowDialogResult(IWindowManager windowManager, IScopedContext<TModel> scope) { this.windowManager = windowManager; this.scope = scope; } public ShowDialogResult<TModel> Configure(Action<TModel> configure) { this.configure = configure; return this; } public override void Execute(CoroutineExecutionContext context) { if(configure != null) configure(scope.EntryPoint); using(scope) { windowManager.ShowDialog(scope.EntryPoint); } base.Execute(context); } public TModel Model { get { return scope.EntryPoint; } } }
Its then used like this from domain
public IEnumerable<IResult> ShowPluginSettings(PluginSettingsMenuViewModel pluginMenu) { yield return resultFactory.ShowDialog<PluginSettingsViewModel>() .Configure(p => p.Init(pluginMenu.PluginSetting)); }
This will result in that Sub ViewModels like above dialog can have their own sub scope which enables local Event Aggregators and other resources that only should live during a certain lifetime and only be active in the current object graph (Called EntryPoint above)
One comment