DEV Community

Cover image for How to Inject Service from DI Container to Marten Async Projection
Julia Shevchenko
Julia Shevchenko

Posted on

How to Inject Service from DI Container to Marten Async Projection

Recently, I had a need to inject a service directly from the DI Container to the Marten Async projection. The issue is that Marten async projections are run in separate scopes, so they cannot reach services from other scopes.
It appeared to be tricky, and I couldn't find any information in the documentation on how to overcome it.

Marten async projections VS DI

Marten runs async projections in the background daemon.
These projections are run out of any HTTP request or message handler scope, so it's not possible to inject scoped services directly.

ILogger, for example, is usually registered as a singleton, so Marten allows it to be injected into async projections.

Typical Configuration

This is how async projection configuration usually looks:

builder.Services.AddMarten(storeOptions => {})
  .AddProjectionWithServices<MyProjection>(
      ProjectionLifecycle.Async,
      ServiceLifetime.Scoped
  );
Enter fullscreen mode Exit fullscreen mode

Also, it could be like this:

builder.Services.AddMarten(storeOptions => {
  storeOptions.Projections.Add<TripProjectionWithCustomName>(ProjectionLifecycle.Async);
}) 
Enter fullscreen mode Exit fullscreen mode

This lambda runs at container build time, not inside a request scope, so there is no direct access to the DI container.

The Workaround

The possible way to solve it is to inject the ServiceProvider to use it as a Service Locator and get the service directly from it. It's not good practice, though.
I was lucky to find this GitHub Issue, which gave me a clue how to proceed with it.
There is an overload of AddMarten that injects the serviceProvider.
Here is what you need: create an instance of your projection, pass IServiceProvider or any other service as a parameter, and add this instance to the Marten with Projections.Add. To be able to get the ServiceProvider - return the StoreOptions.

builder.Services.AddMarten(serviceProvider =>
{
  var options = new StoreOptions();

  MyProjection mytProjection = new(
      serviceProvider.GetRequiredService<ILogger<MytProjection>>(), 
      serviceProvider.GetRequiredService<IServiceProvider>());
  options.Projections.Add(mytProjection, ProjectionLifecycle.Async);

  return options;
};
Enter fullscreen mode Exit fullscreen mode

When you return options, you’re handing your fully configured StoreOptions back to Marten so it can build the document store. Marten calls this lambda after the service container is created, so the serviceProvider argument is already a fully built root container.


Hope this article was helpful and saved you some time!

Have you ever worked with Event Sourcing and the Marten framework in particular? How do you like this experience?

Top comments (0)