Steve Dunn
Steve Dunn

Reputation: 21741

How should I handle Simple Injector Lifestyles for MediatR NotificationHandlers?

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...

Upvotes: 0

Views: 966

Answers (1)

Steven
Steven

Reputation: 172606

You basically have three options:

  • Define your own abstraction (as you are currently doing)
  • Replace the default IMediator implementation with one that applies scoping
  • Decorate the default IMediator implementation with a class that applies scoping

All 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.

Upvotes: 1

Related Questions