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
);
Also, it could be like this:
builder.Services.AddMarten(storeOptions => {
storeOptions.Projections.Add<TripProjectionWithCustomName>(ProjectionLifecycle.Async);
})
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;
};
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)