I have an ASP.NET Web API using MediatR and SimpleInjector.
They are registered like this:
_container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
_container.Options.DefaultLifestyle = Lifestyle.Scoped;
_container.Collection.Register(typeof(INotificationHandler<>), typesFound);
I can publish events from my Controllers
:
[HttpGet]
public ActionResult<...> Get()
{
_mediator.Publish(new SomeEvent());
}
That works perfectly!
A new requirement is to listen for updates from an external system (Tibco). When an update occurs, the notification comes in via a C# event from another thread. In the C# event handler, I want to use MediatR to Publish
a notification:
void callbackFromTibco(object listener, MessageReceivedEventArgs @args)
{
_mediatr.Publish(new SomeEvent();
}
It's at this point that SimpleInjector throws an exception:
SomeEventHandler is registered as 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope
.
That's because the call stack originates from another thread whereas in the Controller
, the Controller itself was Scoped by SimpleInjector and hence MediatR creates the handler under the same scope.
Is it possible to do something so that I can register event handlers to be used in both circumstances?
I've gotten around this by creating an IPublishEvents
interface and a PublishEvents
class, where the PublishEvents
class looks like:
public PublishEvents(Container container, IMediator mediator)
{
_container = container;
_mediator = mediator;
}
public Task Publish(object notification, CancellationToken cancellationToken = default)
{
using (AsyncScopedLifestyle.BeginScope(_container))
{
return _mediator.Publish(notification, cancellationToken);
}
}
Is the abstraction the right approach? It certainly meets the Don't Marry the Framework
mantra, but aside from that, I'd like to know if there's a better way...
You basically have three options:
IMediator
implementation with one that applies scopingIMediator
implementation with a class that applies scopingAll three options are equally good, although defining your own application abstractions should typically have your preference, as that is in accordance to the Dependency Inversion Principle.