I Have a MediatR Notification handler. This notification is called when a request is made from the user, so I want to process the event in the background. From MediatR wiki`s im take a class for parallel call of handlers without waiting for their completion. See here.
The delay for a second is needed to allow the request to complete and demonstrate my problem. Without delay, it happens randomly.
The repository is transferred by injection, everything is fine with it. But the DbContext from EFCore in it is marked as disposed after the request from the user is completed. Because of this, it is not possible to save the result.
I just want to make this job in background.
Code injection of Mediator and Repository:
builder.RegisterGeneric(typeof(EfRepository<>))
.As(typeof(IRepository<>))
.As(typeof(IReadRepository<>))
.InstancePerLifetimeScope();
builder
.RegisterType<CustomMediator>()
.As<IMediator>()
.InstancePerLifetimeScope();
builder.Register<ServiceFactory>(context =>
{
var c = context.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
var mediatrOpenTypes = new[]
{
typeof(IRequestHandler<,>),
typeof(IRequestExceptionHandler<,,>),
typeof(IRequestExceptionAction<,>),
typeof(INotificationHandler<>),
};
foreach (var mediatrOpenType in mediatrOpenTypes)
{
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
Code injection of DbContext:
public static void AddDbContext(this IServiceCollection services, string connectionString) =>
services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionString)); // will be created in web project root
Code of method PublishCore from my CustomMediator:
protected override Task PublishCore(IEnumerable<Func<INotification, CancellationToken, Task>> allHandlers, INotification notification, CancellationToken cancellationToken)
{
foreach (var handler in allHandlers)
{
_ = Task.Run(() => handler(notification, cancellationToken));
}
return Task.CompletedTask;
}
Code of method Handle from my INotificationHandler
public async Task Handle(NewCaptchaAddedEvent domainEvent, CancellationToken cancellationToken)
{
Guard.Against.Null(domainEvent, nameof(domainEvent));
var captcha = domainEvent.Captcha;
await Task.Delay(1000);
var solution = await _vkCaptchaSolver.SolveCaptcha(captcha.CaptchaUri);
captcha.MarkSolved(solution);
await _repository.UpdateAsync(captcha);
return;
}
Update 1 I found the reason for this behavior. The whole point is in LifetimeScope, MediatR uses the current one when creating, and this is the scope of the web request, so all the children are destroyed. I made a small hack. It's very dirty. Just creates a new scope in the root scope when mediator wants to recieve services:
builder.Register<ServiceFactory>(context =>
{
var c = context.Resolve<ILifetimeScope>();
var newl = (c as LifetimeScope).RootLifetimeScope.BeginLifetimeScope();
return t => newl.Resolve(t);
});
But this is rather a temporary solution, the question remains, how to create a scope correctly or not to destroy it as a child?