I was playing around with ASP.NET Core 3.1 and tried to implement Dependency Injection flavor called Pure DI, i.e. without dependency container (even the built in one) to instantiate Controllers. You can see entire code here, but here's the rundown: I know I have to replace the default IControllerActivator
but the main issue concerns singleton IDisposable
dependencies, which have to be held by the Composition Root for entire application lifetime and disposed when the application shuts down (I know it's not strictly necessary, but it's a good practice - I've seen some funky implementations of database bulk-inserts which flush data in the Dispose
method :S). My idea then is based on implementing a custom ControllerActivator
(the Composition Root) with a Singleton pattern, so I can access it and dispose of it when the application shuts down. Here's the Program.cs
(I trimmed some comments and namespace imports to save space):
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
try
{
await host.RunAsync();
}
finally
{
/* Dispose singletons held in ControllerActivator when application shuts down. */
ControllerActivator.Singleton.Dispose();
}
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}
And here's the ControllerActivator
:
public sealed class ControllerActivator
: IControllerActivator, IDisposable
{
internal static readonly ControllerActivator Singleton = new ControllerActivator();
private readonly List<IDisposable> _singletonDisposables = new List<IDisposable>();
/// <summary>
/// An example of a singleton, disposable object used in controller's dependency graph.
/// </summary>
private readonly DisposableDependency _singletonDisposableDependency;
public ControllerActivator()
{
_singletonDisposableDependency = RegisterSingletonForDispose(new DisposableDependency());
}
object IControllerActivator.Create(ControllerContext context)
{
if (GetControllerType(context) == typeof(HelloController))
{
var scopedDependency = RegisterForDispose(context, new DisposableDependency());
return new HelloController(
_singletonDisposableDependency,
scopedDependency);
}
throw new InvalidOperationException("Unknown Controller Type");
}
void IControllerActivator.Release(ControllerContext context, object controller)
{
}
public void Dispose()
{
foreach (var disposable in _singletonDisposables)
{
disposable.Dispose();
}
}
private T RegisterSingletonForDispose<T>(T disposableSingleton)
where T : IDisposable
{
_singletonDisposables.Add(disposableSingleton);
return disposableSingleton;
}
private Type GetControllerType(ControllerContext context)
{
return context.ActionDescriptor.ControllerTypeInfo.AsType();
}
private T RegisterForDispose<T>(ActionContext context, T scopedDisposable)
where T : IDisposable
{
context.HttpContext.Response.RegisterForDispose(scopedDisposable);
return scopedDisposable;
}
}
Please take a look in the linked GitHub repo to get the full picture.
Now to my questions: Can you see any potential problems with such implementation? Is there a better, more "standard" way to do this?
Note: plugging into IApplicationLifetime
in Startup
is deprecated in ASP.NET Core 3.1, that's why I utilized Program's finally clause to dispose ControllerActivator
.
TIA