Multiple HostedServices with common base class - c#

I'm trying to set up multiple hosted services that all share some base functionality, but differ in some configuration that they use to set things up (like connection strings, urls, values, or whatever). I would like to achieve something like this in Program.cs:
services.AddHostedService<CustomHostedService1>(config =>
{
config.Value = 1234;
});
services.AddHostedService<CustomHostedService2>(config =>
{
config.Value = 5678;
});
The base class and derived classes would look something like this:
public abstract class BaseHostedService : BackgroundService
{
public MyConfigObject Config { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
ConfigureBaseClass(Config);
DoWork();
}
private void ConfigureBaseClass(MyConfigObject config)
{
// Use config to do something
}
public abstract void DoWork();
}
public class CustomHostedService1 : BaseHostedService
{
private readonly ISomeService1 _someService;
public CustomHostedService1(ISomeService1 someService)
{
_someService = someService;
}
public override void DoWork()
{
// Do some work here
_someService.DoWork();
}
}
public class CustomHostedService2 : BaseHostedService
{
private readonly ISomeService2 _someService;
private readonly ILogger<CustomHostedService2> _logger;
public CustomHostedService2(ILogger<CustomHostedService2> logger, ISomeService2 someService)
{
_someService = someService;
_logger = logger;
}
public override void DoWork()
{
// Do some work here
_someService.DoWork();
}
}
What would be a good pattern to achieve this? Note that the two derived classes might have different dependencies.

Related

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()
}
}

How to add parameter to dependency injection via HostBuilder?

I have a simple HostBuilder setup:
private void BuildServices(IServiceCollection services) {
services.AddHttpClient<IApiClient, ApiClient>();
}
... where:
class ApiClient : IApiClient {
public ApiClient(HttpClient httpClient, ClientOptions options) {
// do stuff
}
}
I have a ClientOptions object configured. How do I register it so it will be injected into ApiClient?
I am going to give this a shot, but I am not 100% sure this is what you are asking for.
Typically, you would inject it in to your pipeline. So let's say you have ClientOptions set up this way:
ClientOptions.cs
public interface IClientOptions
{
int DeriveSomeValue();
}
public sealed class ClientOptions : IClientOptions
{
public int DeriveSomeValue() => 42;
}
You would then inject it in to your pipeline:
services.AddTransient<IClientOptions, ClientOptions>();
// or: services.AddScoped<IClientOptions, ClientOptions>();
// or: services.AddSingleton<IClientOptions, ClientOptions>();
Once that is done, you can inject it in to IApiClient like so:
public sealed class ApiClient : IApiClient {
private readonly IClientOptions _clientOptions;
public ApiClient(HttpClient httpClient, IClientOptions options) {
_clientOptions = options;
var myDerivedValue = _clientOptions.DeriveSomeValue();
}
}
I am hoping that's what you are asking, if not, please let me know and I can clarify.

(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
}

How to fix DbContext has been disposed for an async scenario?

We have upgraded our application from NserviceBus v5 to v6 and after that we run into major problem, most of the time receiving the following error.
The operation cannot be completed because the DbContext has been
disposed.
It was not obvious until we got load to our system.
We are running with eight concurrent threads and by that we receive the above error.
public class EndpointInitializer
{
public void Initialize(IKernel container)
{
var endpointConfiguration = new EndpointConfiguration("MyEndpoint");
endpointConfiguration.UseContainer<NinjectBuilder>(
customizations => { customizations.ExistingKernel(container); });
//More settings...
}
}
.
public class MyMessageHandler : IHandleMessages<MyCommand>
{
private readonly IPersonRepository _personRepository;
public MyMessageHandler(IPersonRepository personRepository)
{
_personRepository = personRepository;
}
public async Task Handle(MyCommand message, IMessageHandlerContext context)
{
var person = await _personRepository.GetByIdentifierAsync(message.Identifier).ConfigureAwait(false);
//More code...
await _personRepository.UpdateAsync(person);
}
}
.
[Serializable]
public class MyCommand
{
public string Identifier { get; set; }
}
.
public class DependencyRegistrar
{
public IKernel Container { get; set; }
public void Create()
{
Container = new StandardKernel();
RegisterTypes(Container);
}
public void RegisterTypes(IKernel kernel)
{
kernel.Bind<IPersonRepository>().To<PersonRepository>();
kernel.Bind<DbContext>().To<MyDbContext>().InThreadScope();
//More registrations...
}
}
.
public class MyDbContext : DbContext
{
public MyDbContext() : base("MyConn")
{
}
}
.
public interface IPersonRepository
{
Task<Person> GetByIdentifierAsync(string identifier);
Task UpdateAsync(Person entity);
//More methods...
}
.
public class PersonRepository : IPersonRepository
{
private readonly DbContext _dbContext;
public PersonRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Person> GetByIdentifierAsync(string identifier)
{
var personList = await _dbContext.Set<Person>().Where(x => x.Identifier == identifier).ToListAsync().ConfigureAwait(false);
//More code...
return personList.SingleOrDefault();
}
public async Task UpdateAsync(Person entity)
{
//More code...
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
.
public class Person
{
public int Id { get; set; }
public string Identifier { get; set; }
//More properties...
}
One option that we noticed is working is to pick up DataContext using Particulars example to use UnitOfWorkSetupBehavior. But it does not fit that well in our scenario because we have a complicated setup with services and repositories injecting the DbContext in the constructor throughout our application.
Ie, the (partial) solution for now is to call the method on the repository like;
var person = await _personRepository.GetByIdentifierAsync(context.DataContext(), message.Identifier).ConfigureAwait(false);
But now, when we run inte more complicated scenarios this won¨t suffice.
So, what are we missing? What is really the issue here?
Ninject PerThreadScope uses System.Threading.Thread.CurrentThread. With async in place the thread can change potentially for every continuation (the code that follows an await statement). You can either use a custom named scope, an async local scope or use the InUnitOfWorkScope from NServiceBus.Ninject.

Categories

Resources