Abstract DI container Scopes

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)

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s