danielwertheim

danielwertheim


notes from a passionate developer

Developer that lives by the mantra "code is meant to be shared".

Share


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

MediatR and Autofac - be aware of scopes

Daniel WertheimDaniel Wertheim

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

Developer that lives by the mantra "code is meant to be shared".

Comments