With the release of v0.3.0
the internal routing as well as the strategy for creating handlers got access to a MessageEnvelope
. The envelope carries the message and custom state. This custom state can with middlewares be used to e.g. assign a certain Autofac child lifetime scope, that later on is used when resolving the handlers. When the message has been routed, you dispose the scope in the middleware. Thereby you get per-request scoped handlers.
Before continuing, it might be worth to read the short post about "Routemeister and middlewares"
Code sample
Full code sample exists in this gist.
Why
Well, by not doing this, the handlers that are created, will not get disposed until the context goes out of scope. If you are unlucky, this can be the root container itself. Then what happens if the message is routed to a handler that has a dependency on e.g. a TCP connection? That will set there... waiting...
The Scene
Lets say we have a simple handler ConcreteHandler
that handles a simple message MyMessage
. The handler also has a dependency on SomeDependency
, which we want resolved by the IoC-container, in this case Autofac. SomeDependency
implements IDisposable
so that we can see that it will be released. This gives us something like this:
public class MyMessage { }
public class ConcreteHandler :
ISomeMessageHandler<MyMessage>,
IDisposable
{
private readonly SomeDependency _dependency;
public ConcreteHandler(SomeDependency dependency)
{
_dependency = dependency;
}
public async Task HandleAsync(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.");
}
}
Let's just get a consuming ConsoleApplication
, that will route two messages. Hence we expect the handler and SomeDependency
to be instantiated twice.
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules(typeof(Program).Assembly);
var container = builder.Build();
var router = container.Resolve<IAsyncMessageRouter>();
router.RouteAsync(new MyMessage()).Wait();
router.RouteAsync(new MyMessage()).Wait();
}
}
Bootstrap the Autofac module
I'm making the assumption that you have Autofac bootstraped and all we do know is registering a Autofac module, with Routemeister.
public class OurModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//This is where we will add stuff
}
}
First lets just register the handler and the dependency. We do this manually now:
builder.RegisterType<SomeDependency>();
builder.RegisterType<ConcreteHandler>();
Next step. Register the routes:
//MessageRoutes
builder.Register(ctx =>
{
var factory = new MessageRouteFactory();
return factory.Create(
typeof(Program).Assembly,
typeof(ISomeMessageHandler<>));
})
.SingleInstance();
Next step. Register the router:
//Router
builder.Register(ctx =>
{
var routes = ctx.Resolve<MessageRoutes>();
var parentScope = ctx.Resolve<ILifetimeScope>();
var router = new MiddlewareEnabledAsyncMessageRouter(
(type, envelope) => envelope.GetScope().Resolve(type),
routes);
router.Use(next => envelope =>
{
using (var childScope = parentScope.BeginLifetimeScope())
{
envelope.SetScope(childScope);
return next(envelope);
}
});
return router;
})
.As<IAsyncMessageRouter>()
.SingleInstance();
The trick here is to make use of the router.Use
method. It lets you intercept routing calls. And the MessageEnvelope
argument it gets, contains the message and also a state bag to which you can assign state:
envelope.SetState("someKey", someValue);
To simplify this, I made an extension method:
internal static class MessageEnvelopeExtensions
{
internal static void SetScope(
this MessageEnvelope envelope, ILifetimeScope scope)
{
envelope.SetState("scope", scope);
}
internal static ILifetimeScope GetScope(
this MessageEnvelope envelope)
{
return envelope.GetState("scope") as ILifetimeScope;
}
}
We use this to create a "child scope" in Autofac, in a using block and inside it, we let the routing flow continue by invoking next(envelope)
:
router.Use(next => envelope =>
{
using (var childScope = parentScope.BeginLifetimeScope())
{
envelope.SetScope(childScope);
return next(envelope);
}
});
Now lets run the app:
That's it. It looks extensive. But look at the whole code (gist here) and you will see that it's not that bad. At least doable. And something you do once.
Cheers,
//Daniel