While building Routemeister I got my eyes open for another project that acts in the same domain. I've written about this before, and as I bump into use-cases and solve them in Routemeister, I try to follow samples and docs in MediatR to see how it behaves in those situations. Time has come to see how MediatR behaves with Autofac.
The TL;DR; for me MediatR falls short here. Not cleaning up resources as I thought it would. But again, I'm just following the sample and I'm not an expert on either MediatR or Autofac. So feel free to comment. Note. I'm not seeing injecting Func<SomeDependency>
as an option in this case. Bottom line. Try, test and investigate how the bits and pieces works. Don't just take it for granted.
Lets say you have handlers that take a dependency on something that opens e.g. a TCP-connection. This dependency implements IDisposable
and you think life is good and that Autofac will clean it. Yes it will. If you give it a fair chance to clean it, by actually disposing the scope.
Since Routemeister can be configured to handle this, I turned to MediatR to see how you handle it there. Looking at the sample code in MediatR and using the same boilerplate as in the Routemeister sample, I came up with this (gist here):
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules(typeof(Program).Assembly);
var container = builder.Build();
var router = container.Resolve<IMediator>();
router.PublishAsync(new MyMessage()).Wait();
router.PublishAsync(new MyMessage()).Wait();
}
}
public class MyMessage : IAsyncNotification { }
public class ConcreteHandler :
IAsyncNotificationHandler<MyMessage>,
IDisposable
{
private readonly SomeDependency _dependency;
public ConcreteHandler(SomeDependency dependency)
{
_dependency = dependency;
}
public async Task Handle(MyMessage message)
{
await _dependency.DoWorkAsync();
}
public void Dispose()
{
Console.WriteLine("I am being released");
}
}
public class SomeDependency : IDisposable
{
public Task DoWorkAsync()
{
Console.WriteLine("Doing work with underlying connection");
return Task.FromResult(0);
}
public void Dispose()
{
Console.WriteLine("I'm cleaning the underlying connection.");
}
}
public class OurModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterSource(new ContravariantRegistrationSource());
builder.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>)c
.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
});
builder.RegisterType<Mediator>()
.As<IMediator>()
.SingleInstance();
builder.RegisterType<SomeDependency>();
builder.RegisterType<ConcreteHandler>()
.As<IAsyncNotificationHandler<MyMessage>>();
}
}
Result...
With this in place it will not get a chance to clean up the resources until the outer most container will be disposed. Pretend this was running as an in-process routing in conjunction with a long lived service. That would basically end up in you, creating more and more instances of, in this case SomeDependency
. Yes, I know, as a workaround, you can take a dependency on Func<SomeDependency>
and in your handler use that with a using
block. But that was not the use-case I was looking into now.
Bottom line. Try, test and investigate how the bits and pieces works. Don't just take it for granted.
Feel free to comment and educate me in what I'm doing wrong.
Cheers,
//Daniel