AddDbContext conditional by Request-Parameters - c#

I´ve got a configuration-class which holds database connection-strings per mandant. But only with the requests parameters the mandant will be clear. So I want to inject the right DbContext contitional by mandant.
So far I´ve the following problem:
public class MessageController : IMessageController
{
private readonly IMessageParser _parser;
private readonly ILogger _log;
private readonly IMessageProcessor _receiver;
public MessageController(IMessageParser parser, IMessageProcessor receiver, ILogger log)
{
_parser = parser;
_log = log;
_receiver = receiver;
}
public async Task<Response> MessageReceivedEvent(Request request)
{
if (!_parser.TryParseMessage(request.SomeInlineData, out var mandant))
{
_log.LogError("The given Message could not be parsed");
throw new InvalidOperationException("The given Message could not be parsed");
}
// what to do with the mandant?
_receive.Received(request);
return new Response();
}
}
The receiver may has the following logic:
public class MessageProcessor : IMessageProcessor
{
// this database should be injected dependend on the current mandant
private readonly DbContext _database;
public MessageProcessor(DbContext database)
{
_database = database;
}
public void Received(Request request)
{
// Do fancy stuff
_database.SaveChanges();
}
}
Now here the ConfigureServices:
services.AddDbContext<DbContextX>((provider, options) => options.UseSqlite($"Data
Source={Path.GetFullPath("How to get the right mandant connection string?")}"))
.Configure<MandantConfiguration>(Configuration.GetSection(nameof(MandantConfiguration)))
Here the configuration class:
public class MandantConnection : IMandantConnection
{
public string DatabaseConnection { get; set; }
}
public class MandantConfiguration : IMandantConfiguration
{
public Dictionary<Mandant, MandantConnection> Mandants { get; set; }
}
EDIT:
The DbContext is injected as Scoped, so I think it should be possible to change the Connection-String per Scope but I don´t know how.

The trick is to use the HttpContext within the request.
So far here my solution for the given Problem:
public class MessageController : IMessageController
{
private readonly IMessageParser _parser;
private readonly ILogger _log;
private readonly IMessageProcessor _receiver;
private readonly IHttpContextAccessor _accessor;
public MessageController(IMessageParser parser, IMessageProcessor receiver, ILogger log, IHttpContextAccessor accessor)
{
_parser = parser;
_log = log;
_receiver = receiver;
_accessor = accessor;
}
public async Task<Response> MessageReceivedEvent(Request request)
{
if (!_parser.TryParseMessage(request.SomeInlineData, out var mandant))
{
_log.LogError("The given Message could not be parsed");
throw new InvalidOperationException("The given Message could not be parsed");
}
// ---> Thats to do
_accessor.HttpContext.Items[nameof(Mandant)] = mandant;
_receive.Received(request);
return new Response();
}
}
Then I´ve implemented a MandantService, which injects the Accessor also:
public class MandantenService : IMandantenService
{
public IMandantConnection CurrentConfiguration { get; set; }
private readonly MandantConfiguration _configuration;
public MandantenService(IOptions<MandantConfiguration> options, IHttpContextAccessor accessor)
{
_configuration = options.Value;
CurrentConfiguration = _configuration.Mandants[Enum.Parse<Mandant>(accessor.HttpContext.Items[nameof(Mandant)].ToString())];
}
}
Then I can use this service within the DbContext:
public VdvKaDbContext(DbContextOptions<VdvKaDbContext> options, IMandantenService mandantenService)
: base(options)
{
_mandantenService = mandantenService;
...
}
And configure the Sqlite Database in the OnConfigure-Method:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite($"Data Source={Path.GetFullPath(_mandantenService.CurrentConfiguration.DatabaseConnection)}");
}
And boom every call of scoped database will be the specific mandant database:
using var scope = _provider.CreateScope();
return scope.ServiceProvider.GetService<DbContext>();

Related

Raise event from activity

I'm building a saga state machine, trimmed down implementation below:
public class DueDiligenceCaseCreateStateMachine : MassTransitStateMachine<DueDiligenceCaseCreateState>
{
public State CreatingCase { get; private set; }
public Event<DueDiligenceCaseCreateCommand> TriggerReceived { get; private set; }
public Event CaseCreationFinished { get; private set; }
public Event CaseCreationFailed { get; private set; }
private readonly ILogger<DueDiligenceCaseCreateStateMachine> _logger;
private readonly IOptions<DueDiligenceCaseCreateSagaOptions> _sagaOptions;
public DueDiligenceCaseCreateStateMachine(
ILogger<DueDiligenceCaseCreateStateMachine> logger,
IOptions<DueDiligenceCaseCreateSagaOptions> sagaOptions)
{
_logger = logger;
_sagaOptions = sagaOptions;
Configure();
BuildProcess();
}
private void Configure()
{
Event(
() => TriggerReceived,
e => e.CorrelateById(x => x.Message.DueDiligenceCaseId));
}
private void BuildProcess()
{
During(
Initial,
When(TriggerReceived)
.TransitionTo(CreatingCase)
.Activity(CreateCase));
}
private EventActivityBinder<DueDiligenceCaseCreateState, DueDiligenceCaseCreateCommand> CreateCase(
IStateMachineActivitySelector<DueDiligenceCaseCreateState, DueDiligenceCaseCreateCommand> sel) =>
sel.OfType<CreateCaseActivity>();
}
And the activity itself is here:
public class CreateCaseActivity : BaseActivity<DueDiligenceCaseCreateState, DueDiligenceCaseCreateCommand>
{
private readonly ICommandHandler<InitializeCaseCommand> _initializeCaseHandler;
private readonly IOptions<ApplicationOptions> _options;
private readonly ILogger<DueDiligenceCaseCreateConsumer> _logger;
public CreateCaseActivity(
ICommandHandler<InitializeCaseCommand> initializeCaseHandler,
IOptions<ApplicationOptions> options,
ILogger<DueDiligenceCaseCreateConsumer> logger)
{
_initializeCaseHandler = initializeCaseHandler;
_options = options;
_logger = logger;
}
public override async Task Execute(
BehaviorContext<DueDiligenceCaseCreateState, DueDiligenceCaseCreateCommand> context,
Behavior<DueDiligenceCaseCreateState, DueDiligenceCaseCreateCommand> next)
{
_logger.LogInformation(
"Consuming {Command} started, case id: {caseid}, creating a case...",
nameof(DueDiligenceCaseCreateCommand),
context.Data.DueDiligenceCaseId);
var initializeCaseCmd = ConvertMessageToCommand(context.Data);
initializeCaseCmd.CanHaveOnlyOneActiveCasePerCustomer = !_options.Value.FeatureToggles.AllowMultipleActiveCasesOnSingleCustomer;
try
{
await _initializeCaseHandler.Handle(initializeCaseCmd);
}
catch
{
}
finally
{
await next.Execute(context);
}
}
private InitializeCaseCommand ConvertMessageToCommand(DueDiligenceCaseCreateCommand message) =>
// returns the command object
}
The state machine has two events for now - CaseCreationFinished and CaseCreationFailed. I'd like to raise the first one in the try clause of the activity and the other one in the catch part. I see the context object passed in as an argument has the Raise method, but the problem is that I can't reach the DueDiligenceCaseCreateStateMachine.CaseCreationFinished from within the activity. Is there a way to do it?
There is a Raise method on BehaviorContext, why not use it?
Updated
You can add a dependency on your activity for the state machine itself, which would give you access to the events.

How to share Context data between MassTransit Consumers while using the `IBusControl` for publishing messages?

I am using MassTransit 7.2.2 in a .NET Core application with RabbitMQ(for local development) and SQS(for deployment) where a single message processing can result in multiple new messages getting created and processed.
All the messages share the same base type
public class BaseMessage : CorrelatedBy<Guid>
{
public BaseMessage()
{
CorrelationId = Guid.NewGuid();
CreationDate = DateTime.UtcNow;
}
public Guid CorrelationId { get; init; }
public DateTime CreationDate { get; }
public Guid? ConversationId { get; set; }
}
The basic flow of processing is same for all messages, there is a Service per Consumer.
public class FirstMessage : BaseMessage
{
}
public class FirstConsumer : IConsumer<FirstMessage>
{
private readonly ILogger<FirstConsumer> _logger;
private readonly FirstService _service;
public FirstConsumer(ILogger<FirstConsumer> logger, FirstService service)
{
_logger = logger;
_service = service;
}
public Task Consume(ConsumeContext<FirstMessage> context)
{
_logger.LogInformation($"FirstConsumer CorrelationId: {context.CorrelationId} and ConversationId: {context.ConversationId} and InitiatorId: {context.InitiatorId}");
_service.Process(context.Message);
return Task.CompletedTask;
}
}
public class FirstService
{
private readonly IBusControl _busControl;
private readonly ILogger<FirstService> _logger;
public FirstService(IBusControl busControl, ILogger<FirstService> logger)
{
_busControl = busControl;
_logger = logger;
}
public Task Process(FirstMessage firstMessage)
{
var secondMessage = new SecondMessage();
_busControl.Publish(secondMessage);
return Task.CompletedTask;
}
}
The above code is an example and the actual code base has 30+ consumers and all have the same pattern, i.e there is a Service per Consumer and the message is passed to the Service for processing.
I am trying to implement a solution for tracing messages end to end by using the Ids.
ConversationId - Unique Id for tracing logs of all Consumers in a graph
CorrelationId - Unique Id for tracing logs within a Consumer
InitiatorId - Parent Id
There is a message processing graph that looks like
FirstConsumer -> SecondConsumer -> ThirdConsumer.
I have the following Filters
ConsumeFilter
public class SimpleConsumeMessageFilter<TContext, TMessage> : IFilter<TContext>
where TContext : class, ConsumeContext<TMessage>
where TMessage : class
{
public SimpleConsumeMessageFilter()
{
}
public async Task Send(TContext context, IPipe<TContext> next)
{
LogContext.PushProperty("CorrelationId", context.CorrelationId);
LogContext.PushProperty("ConversationId", context.ConversationId);
LogContext.PushProperty("InitiatorId", context.InitiatorId);
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("consume-filter");
}
}
public class SimpleConsumeMessagePipeSpec<TConsumer, TMessage> : IPipeSpecification<ConsumerConsumeContext<TConsumer, TMessage>>
where TConsumer : class
where TMessage : class
{
public void Apply(IPipeBuilder<ConsumerConsumeContext<TConsumer, TMessage>> builder)
{
builder.AddFilter(new SimpleConsumeMessageFilter<ConsumerConsumeContext<TConsumer, TMessage>, TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimpleConsumePipeSpecObserver : IConsumerConfigurationObserver
{
public void ConsumerConfigured<TConsumer>(IConsumerConfigurator<TConsumer> configurator)
where TConsumer : class
{
}
public void ConsumerMessageConfigured<TConsumer, TMessage>(IConsumerMessageConfigurator<TConsumer, TMessage> configurator)
where TConsumer : class
where TMessage : class
{
configurator.AddPipeSpecification(new SimpleConsumeMessagePipeSpec<TConsumer, TMessage>());
}
}
PublishFilter
public class SimplePublishMessageFilter<TMessage> : IFilter<PublishContext<TMessage>> where TMessage : class
{
public SimplePublishMessageFilter()
{
}
public async Task Send(PublishContext<TMessage> context, IPipe<PublishContext<TMessage>> next)
{
if (context.Headers.TryGetHeader("ConversationId", out object #value))
{
var conversationId = Guid.Parse(#value.ToString());
context.ConversationId = conversationId;
}
else
{
if (context.Message is BaseMessage baseEvent && !context.ConversationId.HasValue)
{
context.ConversationId = baseEvent.ConversationId ?? Guid.NewGuid();
context.Headers.Set("ConversationId", context.ConversationId.ToString());
}
}
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("publish-filter");
}
}
public class SimplePublishMessagePipeSpec<TMessage> : IPipeSpecification<PublishContext<TMessage>> where TMessage : class
{
public void Apply(IPipeBuilder<PublishContext<TMessage>> builder)
{
builder.AddFilter(new SimplePublishMessageFilter<TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimplePublishPipeSpecObserver : IPublishPipeSpecificationObserver
{
public void MessageSpecificationCreated<TMessage>(IMessagePublishPipeSpecification<TMessage> specification)
where TMessage : class
{
specification.AddPipeSpecification(new SimplePublishMessagePipeSpec<TMessage>());
}
}
Added to config via
x.UsingRabbitMq((context, cfg) =>
{
cfg.ConnectConsumerConfigurationObserver(new SimpleConsumePipeSpecObserver());
cfg.ConfigurePublish(ppc =>
{
ppc.ConnectPublishPipeSpecificationObserver(new SimplePublishPipeSpecObserver());
});
cfg.UseDelayedMessageScheduler();
cfg.ConfigureEndpoints(context);
cfg.Host("localhost", rmq =>
{
rmq.Username("guest");
rmq.Password("guest");
});
});
With the above approach the 'CorrelationId' header is lost when the SecondConsumer's filters are run.
I have tried the following change and it seems to flow the Ids across the Consumers.
However, taking this approach will impact large sections of code / tests that rely on the IBusControl interface. I am keeping this as a backup option in case I can't find any other solution.
public class FirstService
{
private readonly ILogger<FirstService> _logger;
public FirstService(ILogger<FirstService> logger)
{
_logger = logger;
}
public Task Process( ConsumeContext<FirstMessage> consumeContext)
{
var secondMessage = new SecondMessage();
consumeContext.Publish(secondMessage);
return Task.CompletedTask;
}
}
Question: Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages ?
Many thanks
As explained in the documentation, consumers (and their dependencies) must use one of the following when sending/publishing messages:
ConsumeContext, typically within the consumer itself
IPublishEndpoint or ISendEndpointProvider, typically used by scoped dependencies of the consumer
IBus, last resort, as all contextual data is lost from the inbound message
As for your final question, "Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages?" the answer is no. The consume context would be needed to access any of the contextual data.

Initialize object without pushing parameters

I have class with constructor for logging and for access to config:
public class SendEmaiServiceProvider
{
private readonly IConfiguration _config;
private readonly IWebHostEnvironment _env;
private readonly ILogger<SendEmaiServiceProvider> _logger;
private readonly string _fromEmailAddress;
public SendEmaiServiceProvider(IConfiguration config, IWebHostEnvironment env, ILogger<SendEmaiServiceProvider> logger)
{
_config = config;
_env = env;
_logger = logger;
_fromEmailAddress = _config.GetValue<string>("AppSettings:Email:FromEmailAddress");
}
public void SayHi()
{
Console.WriteLine("Hi");
}
}
The question is - How to call method SayHi from another class without pushing logger, env and config?
No I initialize new object with parameters, but I sure that it is wrong:
var sendEmaiServiceProvider = new SendEmaiServiceProvider(_config, _env, _logger);
sendEmaiServiceProvider.SayHi();
I can create an empty constructor but I will not have _fromEmailAddress value.
Looks like this is a netcore website. Assuming so, then:
Create an interface for the dependency.
Register the dependency in Startup.cs
Request the dependency as needed from the netcore DI.
public interface ISendEmaiServiceProvider
{
void SayHi()
}
public class SendEmaiServiceProvider : ISendEmaiServiceProvider
{
public void SayHi() { }
}
Then in Startup.cs:
public void ConfigureServices( IServiceCollection services )
{
services.AddScoped<ISendEmaiServiceProvider, SendEmaiServiceProvider>();
}
Then in the Controller (or wherever else DI is used), request it in the .ctor and all the dependencies for SendEmaiServiceProvider will be filled automatically by DI.
public class HomeController : Controller
{
public readonly ISendEmaiServiceProvider _emailService;
public HomeController( ISendEmaiServiceProvider emailService )
{
_emailService = emailService
}
}
That should get you going.
You should use dependency injection here. Better you create an interface here and resolve your 'SendEmaiServiceProvider' on the startup. And then use the interface instead of creating a new instance for SayHi() method.
public interface YourInterface
{
void SayHi()
}
public class SendEmaiServiceProvider : YourInterface
{
public void SayHi()
{
//your code
}
}
On your startup,
public void ConfigureServices( IServiceCollection services )
{
services.AddScoped<YourInterface, SendEmaiServiceProvider>();
}
On your controller/service,
public class YourController : Controller
{
public readonly YourInterface _emailSenderService;
public HomeController( YourInterface emailSenderService )
{
_emailSenderService = emailSenderService
}
public IActionResult SayHI()
{
_emailSenderService.SayHi()
}
}

(Interface) A circular dependency was detected for the service of type

I have 2 interfaces:
public interface IPedidoService
{
UsuarioDrogueria CUsuarioDrogueria(string userId, int idDrogueria);
List<PedidoComboProducto> CPedidosCombosProductos(int idcombo, int idPedido);
}
public interface IEmailService
{
void SendEmailAttachment(string email, string subject, string archive);
void SendNotificationEmail(List<Pedido> pedidos, string email, Drogueria drog);
void SendNotificationEmailADM(Pedido pedido) ;
}
I want to use the functions from IEmailService inside IPedidoService, so I inject it in its constructor when I create the respository.
public class PedidoService : IPedidoService
{
private readonly IEmailService emailService;
public PedidoService(IEmailService e)
{
this.emailService = e;
}
}
Up until here everything works fine, but when I try to do reverse the roles (IPedidoService functions inside IEmailService):
public class EmailService : IEmailService
{
private readonly IPedidoService pedidoSettings;
public EmailService(IPedidoService p)
{
this.pedidoSettings = p;
}
}
I end up getting this exception:
System.InvalidOperationException: A circular dependency was detected for the service of type
'EnvioPedidos.Data.Abstract.IPedidoService'.
EnvioPedidos.Data.Abstract.IPedidoService(EnvioPedidos.PedidoService) ->
EnvioPedidos.Data.Abstract.IEmailService(EnvioPedidos.EmailService) ->
EnvioPedidos.Data.Abstract.IPedidoService
Can anybody help me trace the issue here?
A simple way is to use Lazy<T> class which is based on this blog:
Custom extension method:
public static class LazyResolutionMiddlewareExtensions
{
public static IServiceCollection AddLazyResolution(this IServiceCollection services)
{
return services.AddTransient(
typeof(Lazy<>),
typeof(LazilyResolved<>));
}
}
public class LazilyResolved<T> : Lazy<T>
{
public LazilyResolved(IServiceProvider serviceProvider)
: base(serviceProvider.GetRequiredService<T>)
{
}
}
Configure in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//services.AddSingleton<IPedidoService, PedidoService>();
//services.AddSingleton<IEmailService, EmailService>();
services.AddLazyResolution();
}
Change your implements class:
public class PedidoService : IPedidoService
{
private readonly Lazy<IEmailService> emailService;
public PedidoService(Lazy<IEmailService> e)
{
this.emailService = e;
}
//...
}
public class EmailService : IEmailService
{
private readonly Lazy<IPedidoService> pedidoSettings;
public EmailService(Lazy<IPedidoService> p)
{
this.pedidoSettings = p;
}
//...
}
When you have 2 classes, they cannot reference each other by dependency injection. This is called a circular dependency, as shown by your error. You need a 3rd class that references both services and you can use the methods there.
public class PedidoService
{
public PedidoService()
{
}
}
public class EmailService
{
public EmailService()
{
}
}
public class Container
{
private readonly EmailService emailService;
private readonly PedidoService pedidoService;
public Container(EmailService emailService, PedidoService pedidoService)
{
this.emailService = emailService;
this.pedidoService = pedidoService;
}
//use the services here
}

Dependency Injection with XUnit Mediatr and IServiceCollection

Currently I'm able to handle IServiceCollection to inject mocks for particular services in the following manner.
public class TestClass
{
private IMediator _mediatr;
private void SetupProvider(IUnitOfWork unitOfWork, ILogger logger)
{
configuration = new ConfigurationBuilder().Build();
_services = new ServiceCollection();
_services.AddSingleton(configuration);
_services.AddScoped(x => unitOfWork);
_services.AddSingleton(logger);
_services.AddMediatR(Assembly.Load("Application"));
_services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggerBehaviour<,>));
_mediator = _services.BuildServiceProvider().GetService<IMediator>();
}
[Fact]
public async void UnitTest_Success()
{
var unitOfWork = new Mock<IUnitOfWork>();
var logger = new Mock<ILogger>();
SetupProvider(unitOfWork.Object, logger.Object);
var fixture = new Fixture();
var command = fixture.Create<MediatorCommand>();
unitOfWork.Setup(x => x.Repository.FindAll(It.IsAny<IList<long>>(), It.IsAny<bool?>()))
.ReturnsAsync(new List<Domain.Model>());
var response = await _mediatr.Send(command);
using (new AssertionScope())
{
response.Should().NotBeNull();
response.IsSuccess.Should().BeTrue();
}
}
}
For the following subject under test
public class MediatorCommand : IRequest<CommandResponse>
{
public string Name { get; set ;}
public string Address { get; set; }
}
public class MediatorCommandHandler : IRequestHandler<MediatorCommand, CommandResponse>
{
private readonly ILogger _logger;
private readonly IUnitOfWork _unitOfWork;
public MediatorCommandHandler(IUnitOfWork unitOfWork, ILogger logger)
{
_logger = logger;
_unitOfWork = unitOfWork;
}
public async Task<CommandResponse> Handle(MediatorCommand command, CancellationToken cancellationToken)
{
var result = new CommandResponse { IsSuccess = false };
try
{
var entity = GetEntityFromCommand(command);
await _unitOfWork.Save(entity);
result.IsSuccess = true;
}
catch(Exception ex)
{
_logger.LogError(ex, ex.Message);
}
return result;
}
}
This test runs fine and the unitOfWork and logger mocks are used in the command handlers.
I'm try to move this so that the IServiceCollection construction happens per class instead of each test using the following:
public class SetupFixture : IDisposable
{
public IServiceCollection _services;
public IMediator Mediator { get; private set; }
public Mock<IUnitOfWork> UnitOfWork { get; private set; }
public SetupFixtureBase()
{
UnitOfWork = new Mock<IUnitOfWork>();
configuration = new ConfigurationBuilder().Build();
_services = new ServiceCollection();
_services.AddSingleton(configuration);
_services.AddScoped(x => UnitOfWork);
_services.AddSingleton(new Mock<ILogger>().Object);
_services.AddMediatR(Assembly.Load("Application"));
_services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggerBehaviour<,>));
Mediator = _services.BuildServiceProvider().GetService<IMediator>();
}
public void Dispose()
{
Mediator = null;
_services.Clear();
_services = null;
}
}
public class TestClass : IClassFixture<SetupFixture>
{
protected readonly SetupFixture _setupFixture;
public UnitTestBase(SetupFixture setupFixture)
{
_setupFixture = setupFixture;
}
[Fact]
public async void UnitTest_Success()
{
var fixture = new Fixture();
var command = fixture.Create<MediatorCommand>();
_setupFixture.UnitOfWork.Setup(x => x.Repository.FindAll(It.IsAny<IList<long>>(), It.IsAny<bool?>()))
.ReturnsAsync(new List<Domain.Model>());
var response = await _mediatr.Send(command);
using (new AssertionScope())
{
response.Should().NotBeNull();
response.IsSuccess.Should().BeTrue();
}
}
}
Unfortunately with this method my mocks do not get injected on the command handler. Is there a way to get this to work?
Thank you,
I found the issue and it is not related to moving to IClassFixuture<>. The issue was that I was initializing Mediator on a base class an then adding the mock UnitOfWork on a derived class.
This cause the Mediator initialization to fail because one of the beheviours expected the UnitOfWork which at the time was not yet on the container.
Moving the initialization of Mediator after all the services have been added helped me resolve the issue and now all works as expected.
If you try the same thing, please make sure to include all the services in the container before initializing any objects that require those dependencies.
Thank you all those who had input.

Categories

Resources