I'm trying to make a generic implementation of kafka consumers in .NET. Basically I don't want to have a consumer for each and every type of message. I want a generic consumer and then the logic of handling the message is in actual Handlers.
What I tried:
Create a generic interface for handlers
public interface IKafkaHandler<TKey, TMessage>
{
Task HandleAsync(TKey key, TMessage message);
}
Create a concrete handler
public class ClubMessageHandler : IKafkaHandler<string, ClubMessage>
{
private readonly ILogger _logger;
public ClubMessageHandler(ILogger logger)
{
_logger = logger;
}
public Task HandleAsync(string key, ClubMessage message)
{
_logger.LogInformation($"Let's go {message.Name}");
return Task.CompletedTask;
}
}
Create a generic consumer
public class GenericConsumer<TKey, TMessage> : BackgroundService
where TKey: class
where TMessage : class
{
private readonly IKafkaHandler<TKey, TMessage> _handler;
public GenericConsumer(IKafkaHandler<TKey, TMessage> handler)
{
_handler = handler;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
var conf = new ConsumerConfig
{
GroupId = "st_consumer_group",
BootstrapServers = "localhost:9092",
AutoOffsetReset = AutoOffsetReset.Earliest
};
using (var builder = new ConsumerBuilder<TKey, TMessage>(conf).Build())
{
builder.Subscribe("topic.name");
var cancelToken = new CancellationTokenSource();
try
{
while (true)
{
var consumer = builder.Consume(stoppingToken);
//here I want to inject the correct handler based on TMessage type
_handler.HandleAsync(consumer.Message.Key, consumer.Message.Value);
}
}
catch (Exception)
{
builder.Close();
}
}
return Task.CompletedTask;
}
}
And this is where I kinda got stuck. If I try in Program.cs
builder.Services.AddSingleton<IKafkaHandler<string, ClubMessage>, ClubMessageHandler>();
I get an exception saying it can't resolve the services (I can post the entire message if needed)
In the end what I want to achieve is to be able to have something like
//Inject Handlers
builder.Services.AddSingleton<IKafkaHandler<string, ClubMessage>, ClubMessageHandler>();
builder.Services.AddSingleton<IKafkaHandler<string, PlayerMessage>, PlayerMessageHandler>();
builder.Services.AddSingleton<IKafkaHandler<string, OtherMessage>, OtherMessageHandler>();
//Start background services
builder.Services.AddSingleton<GenericConsumer<string, ClubMessage>, ClubMessageHandler>();
//etc...
At this point I'm not even sure if this is possible(although it probably is)? I'm not really sure what I'm missing here. What's the correct way to have use dependency injection here?
Related
I have an Interface called ICompleteConsumer which has only one method and I have multiple implementations for that interface.
EX:
public class SampleClass1: ICompleteConsumer {
public async Task Complete(Model model) {
// do the work
}
}
public class SampleClass2: ICompleteConsumer {
public async Task Complete(Model model) {
// do the work
}
}
I registered these in startUp like this
services.AddScoped<ICompleteConsumer, SampleClass1>(),
services.AddScoped<ICompleteConsumer, SampleClass2>(),
I am inject these dependencies as below
public class ConsumerHandlerService(string message) {
private readonly ICompleteConsumer completeConsumer;
public ConsumerHandlerService(ICompleteConsumer completeConsumer) {
this.completeConsumer = completeConsumer
}
switch (MessageType) {
case (MessageType .1) //I want SampleClass1 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
case (MessageType .2) // I want SampleClass2 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
}
}
How do I achieve this using .net core DI?
There are actually two points of discussion in your question. One is a point related to the design of the code, the other one is a point related on how to use the .NET core DI container in order to handle the required registrations.
Both of them are important, but we need to treat them one at a time.
How to organize the code
To solve your problem in a clean and extensibile way you need to use a design pattern known as the composite design pattern. In order to do so, you need to change the definition of your interface to the following:
public interface IMessageConsumer
{
bool CanHandleMessage(Message message);
Task HandleMessage(Message message);
}
Your interface implementations are then changed as follows:
public class FooMessageConsumer: IMessageConsumer
{
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return message.Type == "foo";
}
public Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException($"{nameof(FooMessageConsumer)} can only handle foo messages.");
await Task.Delay(100).ConfigureAwait(false);
Console.Writeline($"Message {message.Id} handled by {nameof(FooMessageConsumer)}");
}
}
public class BarMessageConsumer: IMessageConsumer
{
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return message.Type == "bar";
}
public Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException($"{nameof(BarMessageConsumer)} can only handle bar messages.");
await Task.Delay(100).ConfigureAwait(false);
Console.Writeline($"Message {message.Id} handled by {nameof(BarMessageConsumer)}");
}
}
At this point you need to introduce a special message consumer, which will be used to dispatch the message to the proper consumer. This is called the composite message consumer and this is the implementation of IMessageConsumer that you will register in your DI container and that will be injected in all the classes which need a message consumer in order to do their business.
public class CompositeMessageConsumer : IMessageConsumer
{
private readonly IMessageConsumer[] _consumers;
public CompositeMessageConsumer(IEnumerable<IMessageConsumer> consumers)
{
if (consumers is null)
throw new ArgumentNullException(nameof(consumers));
this._consumers = consumers.ToArray();
}
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return this._consumers.Any(c => c.CanHandleMessage(message));
}
public async Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException("None of the available consumers is able to handle the provided message.");
var consumer = this._consumers.First(c => c.CanHandleMessage(message));
await consumer.HandleMessage(message).ConfigureAwait(false);
}
}
Here is an example of a class which uses the IMessageConsumer interface. At runtime, the DI container will inject an instance of CompositeMessageConsumer.
// this is an example of a class depending on the IMessageConsumer service
public class MessageProcessor
{
// at runtime this will be an instance of CompositeMessageConsumer
private readonly IMessageConsumer _consumer;
// the DI container will inject an instance of CompositeMessageConsumer here
public MessageProcessor(IMessageConsumer consumer)
{
if (consumer is null) throw new ArgumentNullException(nameof(consumer));
this._consumer = consumer;
}
public async Task ProcessIncomingMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
// do all the pre processing here...
// handle the message
await this._consumer.HandleMessage(message).ConfigureAwait(false);
// do all the post processing here...
}
}
How to register the services on the .NET core DI container
Deciding the proper lifetime for your registrations is a problem that goes beyond the scope of this discussion.
In my example code above I have defined stateless consumer classes and the composite consumer only iterates over the array of the available consumers. The array is never modified during the iteration. This means that all the involved classes are thread safe, so we can register all of them with a singleton lifetime.
That said, the simplest registration that you can perform is the following:
// register the consumers as classes
services.AddSingleton<FooMessageConsumer>();
service.AddSingleton<BarMessageConsumer>();
// register the composite message consumer as an interface, so that when you require IMessageConsumer you get CompositeMessageConsumer
services.AddSingleton<IMessageConsumer>(container =>
{
var fooConsumer = container.GetRequiredService<FooMessageConsumer>();
var barConsumer = container.GetRequiredService<BarMessageConsumer>();
return new CompositeMessageConsumer(new IMessageConsumer[]
{
fooConsumer,
barConsumer
});
});
A great book to learn about these topics is this one. If you are a .NET developer this is definitely a must read.
Try this:
private readonly IEnumerable<ICompleteConsumer> completeConsumers;
public ConsumerHandlerService(IEnumerable<ICompleteConsumer> completeConsumers) {
this.completeConsumers = completeConsumer
}
...
//Get the service you want basing on the messagetype, I guess
var completeConsumer = this.completeConsumers.First(c => c.Type == MessageType); // adjust this to your needs
switch (MessageType) {
case (MessageType .1) //I want SampleClass1 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
case (MessageType .2) // I want SampleClass2 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
}
You can get multiple service by IEnumerable
public class ConsumerHandlerService
{
private readonly IEnumerable<ICompleteConsumer> _completeConsumers;
public ConsumerHandlerService(IEnumerable<ICompleteConsumer> completeConsumers)
{
this.completeConsumers = completeConsumers;
}
public void DoSomeWork(string message)
{
var model = JsonConvert.DeserializeObject<Model>(message);
foreach (var completeConsumer in completeConsumers)
{
completeConsumer.Complete(model);
}
}
}
I have implemented a project on .NET Core 2.2 which consists of 1 console app and 1 class library. Console app is basically a consumer which subscribes to some topics and processes messages that come in. Class library is a database layer, where I have repository. The problem is that sometimes I get error "System.InvalidOperationException: A seconds operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe" when there is interaction with database.
I searched for similar problems, but couldn't find solution that would fix my issue. I have tried registering DbContext and IRepository both as Scoped and Transient, but still keep getting the error. Is it wrong the way I am trying to register DbContext?
This is my code in Consumer.cs
class Consumer
{
static readonly IConfigurationRoot _configuration;
static readonly IServiceProvider _serviceProvider;
static readonly IRepository _repository;
static Consumer()
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
_serviceProvider = new ServiceCollection()
.AddSingleton(_configuration)
.AddTransient<IRepository, Repository>()
.AddDbContext<QueueContext>(options => options.UseNpgsql(_configuration.GetConnectionString("Queue")), ServiceLifetime.Transient)
.BuildServiceProvider();
_repository = _serviceProvider.GetService<IRepository>();
}
static void Main(string[] args)
{
...
RunPoll(here some parameters);
}
private static void RunPoll(here some parameters)
{
...
consumer.OnMessage += async (_, msg) => await ProcessMessageAsync(consumer, msg);
...
}
private static async Task ProcessMessageAsync(here some parameters)
{
...
var message = _repository.GetQueueMessage(msg.Value.Id); // On this line I get exception
if (message == null)
{
message = await _repository.AddQueueMessageAsync(msg.Value.Id); // On this line I get exception
}
while(message.NumTries < msg.Value.MaxNumAttempts)
{
message = await _repository.UpdateQueueMessageTriesAsync(message); // On this line I get exception too
}
...
}
}
This is my code in Respository.cs
public class Repository : IRepository
{
private readonly QueueContext _db;
public Repository(QueueContext db)
{
_db = db;
}
public QueueMessage GetQueueMessage(long id)
{
var message = (from qm in _db.QueueMessages
where qm.Id == id
select qm).FirstOrDefault();
return message;
}
public async Task<QueueMessage> AddQueueMessageAsync(long id)
{
var message = new QueueMessage
{
Id = id,
StartDate = DateTime.Now,
LastTryDate = DateTime.Now,
NumTries = 0
}
_db.QueueMessages.Add(message);
await _db.SaveChangesAsync();
return message;
}
public async Task<QueueMessage> UpdateQueueMessageTriesAsync(QueueMessage message)
{
if (message != null)
{
message.NumTries += 1;
message.LastTryDate = DateTime.Now;
_db.QueueMessages.Update(message);
await _db.SaveChangesAsync();
}
return message;
}
}
This is my code in IRepository.cs
public interface IRepository
{
QueueMessage GetQueueMessage(long id);
Task<QueueMessage> AddQueueMessageAsync(long id);
Task<QueueMessage> UpdateQueueMessageTriesAsync(QueueMessage message);
}
This:
_serviceProvider.GetService
is not Dependency Injection. DI is when you define a constructor (or other method) taking an IRepository. Then you can have a Consumer instance with non-static methods, and the repository is appropriately created when the Consumer is created (or the method is called).
I have a console app which uses a class library to execute some long running tasks. This is a .net core console app and uses the .net core Generic Host. I also use the ShellProgressBar library to display some progress bars.
My Hosted service looks like this
internal class MyHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private readonly IMyService _myService;
private readonly IProgress<MyCustomProgress> _progress;
private readonly IApplicationLifetime _appLifetime;
private readonly ProgressBar _progressBar;
private readonly IProgressBarFactory _progressBarFactory;
public MyHostedService(
ILogger<MyHostedService> logger,
IMyService myService,
IProgressBarFactory progressBarFactory,
IApplicationLifetime appLifetime)
{
_logger = logger;
_myService = myService;
_appLifetime = appLifetime;
_progressBarFactory = progressBarFactory;
_progressBar = _progressBarFactory.GetProgressBar(); // this just returns an instance of ShellProgressBar
_progress = new Progress<MyCustomProgress>(progress =>
{
_progressBar.Tick(progress.Current);
});
}
public void Dispose()
{
_progressBar.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_myService.RunJobs(_progress);
_appLifetime.StopApplication();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Where MyCustomProgress looks like this
public class MyCustomProgress
{
public int Current {get; set;}
public int Total {get; set;}
}
and MyService looks something like so (Job1, Job2, Job3 implement IJob)
public class MyService : IMyService
{
private void List<IJob> _jobsToRun;
public MyService()
{
_jobsToRun.Add(new Job1());
_jobsToRun.Add(new Job2());
_jobsToRun.Add(new Job3());
}
public void RunJobs(IProgress<MyCustomProgress> progress)
{
_jobsToRun.ForEach(job =>
{
job.Execute();
progress.Report(new MyCustomProgress { Current = _jobsToRun.IndexOf(job) + 1, Total = _jobsToRun.Count() });
});
}
}
And IJob is
public interface IJob
{
void Execute();
}
This setup works well and I'm able to display the progress bar from my HostedService by creating a ShellProgressBar instance and using the one IProgress instance I have to update it.
However, I have another implementation of IMyService that I also need to run that looks something like this
public class MyService2 : IMyService
{
private void List<IJob> _sequentialJobsToRun;
private void List<IJob> _parallelJobsToRun;
public MyService()
{
_sequentialJobsToRun.Add(new Job1());
_sequentialJobsToRun.Add(new Job2());
_sequentialJobsToRun.Add(new Job3());
_parallelJobsToRun.Add(new Job4());
_parallelJobsToRun.Add(new Job5());
_parallelJobsToRun.Add(new Job6());
}
public void RunJobs(IProgress<MyCustomProgress> progress)
{
_sequentialJobsToRun.ForEach(job =>
{
job.Execute();
progress.Report(new MyCustomProgress { Current = _jobsToRun.IndexOf(job) + 1, Total = _jobsToRun.Count() });
});
Parallel.ForEach(_parallelJobsToRun, job =>
{
job.Execute();
// Report progress here
});
}
}
This is the one I'm struggling with. when _parallelJobsToRun is executed, I need to be able to create a new child ShellProgressBar (ShellProgressBar.Spawn) and display them as child progress bars of let's say 'Parallel Jobs'.
This is where I'm looking for some help as to how I can achieve this.
Note: I don't want to take a dependency on ShellProgressBar in my class library containing MyService
Any help much appreciated.
I am a little confused by your description, but let's see if I understand what you are up to. So if you wrap all of this in a class, then taskList1 and taskList2 could be class variables. (By the way taskList1/2 should be named better: say parallelTaskList and whatever . . . anyway.) Then you could write a new method on the class CheckTaskStatus() and just iterate over the two class variables. Does that help or have I completely missed your question?
Can you modify it like this?
public Task<ICollection<IProgress<int>>> StartAsync(CancellationToken cancellationToken)
{
var progressList = _myServiceFromLibrary.RunTasks();
return Task.FromResult(progressList);
}
public ICollection<IProgress<int>> RunTasks()
{
var taskList1 = new List<ITask> { Task1, Task2 };
var plist1 = taskList1.Select(t => t.Progress).ToList();
var taskList2 = new List<ITask> { Task3, Task4, Task5 }:
var plist2 = taskList2.Select(t => t.Progress).ToList();
taskList1.foreach( task => task.Run() );
Parallel.Foreach(taskList2, task => { task.Run() });
return plist1.Concat(plist2).ToList();
}
Task.Progress there is probably a progress getter. realistically IProgress should probably be injected via Tasks constructors. But the point is your public interface doesn't accept list of tasks, thus it should just return collection of progress reports.
How to inject progress reporters into your tasks is a different story that depends on tasks implementations and it may or may not be supported. out of the box.
However what you probably should do is to supply progress callback or progress factory so that progress reporters of your choice are created:
public Task StartAsync(CancellationToken cancellationToken, Action<Task,int> onprogress)
{
_myServiceFromLibrary.RunTasks(onprogress);
return Task.CompletedTask;
}
public class SimpleProgress : IProgress<int>
{
private readonly Task task;
private readonly Action<Task,int> action;
public SimpleProgress(Task task, Action<Task,int> action)
{
this.task = task;
this.action = action;
}
public void Report(int progress)
{
action(task, progress);
}
}
public ICollection<IProgress<int>> RunTasks(Action<Task,int> onprogress)
{
var taskList1 = new List<ITask> { Task1, Task2 };
taskList1.foreach(t => t.Progress = new SimpleProgress(t, onprogress));
var taskList2 = new List<ITask> { Task3, Task4, Task5 }:
taskList2.foreach(t => t.Progress = new SimpleProgress(t, onprogress));
taskList1.foreach( task => task.Run() );
Parallel.Foreach(taskList2, task => { task.Run() });
}
you may see here, that it really is mostly question about how your tasks are going to call IProgress<T>.Report(T value) method.
Honestly I would just use an event in your task prototype.
It's not really clear exactly what you want because the code you posted doesn't match the names you then reference in your question text... It would be helpful to have all the code (the RunTasks function for example, your IProgress prototype, etc).
Nevertheless, an event exists specifically to signal calling code. Let's go back to the basics. Let's say you have library called MyLib, with a method DoThings().
Create a new class that inherits from EventArgs, and that will carry your task's progress reports...
public class ProgressEventArgs : EventArgs
{
private int _taskId;
private int _percent;
private string _message;
public int TaskId => _taskId;
public int Percent => _percent;
public string Message => _message;
public ProgressEventArgs(int taskId, int percent, string message)
{
_taskId = taskId;
_percent = percent;
_message = message;
}
}
Then on your library's class definition, add an event like so:
public event EventHandler<ProgressEventArgs> Progress;
And in your console application, create a handler for progress events:
void ProgressHandler(object sender, ProgressEventArgs e)
{
// Do whatever you want with your progress report here, all your
// info is in the e variable
}
And subscribe to your class library's event:
var lib = new MyLib();
lib.Progress += ProgressHandler;
lib.DoThings();
When you are done, unsubscribe from the event:
lib.Progress -= ProgressHandler;
In your class library, now you can send back progress reports by raising the event in your code. First create a stub method to invoke the event:
protected virtual void OnProgress(ProgressEventArgs e)
{
var handler = Progress;
if (handler != null)
{
handler(this, e);
}
}
And then add this to your task's code where you want it:
OnProgress(new ProgressEventArgs(2452343, 10, "Reindexing google..."));
The only thing to be careful about is to report progress sparingly, because each time your event fires it interrupts your console application, and you can really bog it down hard if you send 10 million events all at once. Be logical about it.
Alternate way; If you own the code IProgress<T> and Progress
IProgress<T>
{
IProgress<T> CreateNew();
Report(T progress);
}
Progress<T> : IProgress<T>
{
Progress(ShellProgressClass)
{
// initialize progressBar or span new
}
....
IProgress<T> CreateNew()
{
return new Progress();
}
}
you can later improvise to have one big progressBar (collection of Sequential or Parallel) and what not
Your MyService could have a dependency similar to:
public interface IJobContainer
{
void Add(IJob job);
void RunJobs(IProgress<MyProgress> progress, Action<IJob>? callback = null); // Using an action for extra work you may want to do
}
This way you don't have to worry about reporting progress in MyService (which doesn't feel like it should be MyService's job anyway. The implementation could look something like this for the parallel job container:
public class MyParallelJobContainer
{
private readonly IList<IJob> parallelJobs = new List<IJob>();
public MyParallelJobContainer()
{
this.progress = progress;
}
public void Add(IJob job) { ... }
void RunJobs(IProgress<MyProgress> progress, Action<IJob>? callback = null)
{
using (var progressBar = new ProgressBar(options...))
{
Parallel.ForEach(parallelJobs, job =>
{
callback?.Invoke(job);
job.Execute();
progressBar.Tick();
})
}
}
}
MyService would then look like this:
public class MyService : IMyService
{
private readonly IJobContainer sequentialJobs;
private readonly IJobContainer parallelJobs;
public MyService(
IJobContainer sequentialJobs,
IJobContainer parallelJobs)
{
this.sequentialJobs = sequentialJobs;
this.parallelJobs = parallelJobs;
this.sequentialJobs.Add(new DoSequentialJob1());
this.sequentialJobs.Add(new DoSequentialJob2());
this.sequentialJobs.Add(new DoSequentialJob3));
this.parallelJobs.Add(new DoParallelJobA());
this.parallelJobs.Add(new DoParallelJobB());
this.parallelJobs.Add(new DoParallelJobC());
}
public void RunJobs(IProgress<MyCustomProgress> progress)
{
sequentialJobs.RunJobs(progress, job =>
{
// do something with the job if necessary
});
parallelJobs.RunJobs(progress, job =>
{
// do something with the job if necessary
});
}
The advantage of this way is that MyService only has one job and doesn't have to worry about what you do once the job is completed.
From my understanding of your issue the question is how do you display progress across both completion of the synchronous jobs and parallelized jobs.
In theory the parallel jobs could start and finish at the same time, so you could treat the parallel jobs as a single job. Instead of using the count of sequential jobs as your total, increase that number by one. This might be satisfactory for a small number of parallel jobs.
If you want to add progress between the parallel jobs, you will need to handle multi-threading in your code because the parallel jobs will be running concurrently.
object pJobLock = new object();
int numProcessed = 0;
foreach(var parallelJob in parallelJobs)
{
parallelJob.DoWork();
lock (pJobLock)
{
numProcessed++;
progress.Report(new MyCustomProgress { Current = numProcessed, Total = parallelJobs.Count() });
}
}
So I have a marker interface called IMessage.Then I have classes like:
public class MessageA: IMessage
{
}
Then I have message handlers defined as:
internal interface IMessageHandler<in T> where T: IMessage
{
void Handle(T message);
}
public class MessageAHandler : IMessageHandler<MessageA>
{
public void Handle(T message)
{
//Some logic here
}
}
I want to re-route these messages that I get to the corresponding message handlers when I get a new message. Example:
public class MessageReceiver
{
public void ReceiveMessage(IMessage message)
{
//somehow resolve the appropiate message handler here
messageHandler.Handle(message);
}
}
I can accomplish this right now by using factories like below but I need to have a dependency on each a new factory per different type of message. So I'm wondering if there is a way to create a single factory that will be smart enough to resolve the appropiate message handler?
public class MessageReceiver
{
private readonly Func<IMessageHandler<MessageA>> _messageAFactory;
public MessageReceiver(Func<IMessageHandler<MessageA>> messageAFactory)
{
_messageAFactory= messageAFactory;
}
public void ReceiveMessage(IMessage message)
{
if (message is MessageA)
{
var messageHandler = _messageAFactory();
messageHandler.Handle(message as MessageA);
}
// Add more if-statements here for more messages
}
}
Autofac Registration
public class InfrastructureModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//Register the types in the infrastructure assembly
builder.RegisterAssemblyTypes(ThisAssembly).AsImplementedInterfaces()
.InstancePerLifetimeScope();
//Register the message handlers
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(x => x.IsAssignableFrom(typeof(IMessageHandler<IMessage>)))
.InstancePerDependency().AsImplementedInterfaces();
}
}
First I'll make a small implementation for your messages, just a basic handled flag, in order to test
public class MessageA : IMessage
{
public bool Handled
{
get;
private set;
}
public void MarkAsHandled()
{
this.Handled = true;
}
}
public class MessageB : IMessage
{
public bool Handled
{
get;
private set;
}
public void MarkAsHandled()
{
this.Handled = true;
}
}
Now let's implement both handlers as:
public class MessageAHandler : IMessageHandler<MessageA>
{
public void Handle(MessageA message)
{
message.MarkAsHandled();
}
}
public class MessageBHandler : IMessageHandler<MessageB>
{
public void Handle(MessageB message)
{
message.MarkAsHandled();
}
}
As a side note, you might want to mark your IMessageHandler interface as public (I get compiler error if visibility is set as internal).
Now let's add a small handler:
public interface IMessageHandler
{
Type MessageType { get; }
void Handle(IMessage message);
}
public class MessageHandlerAdapter<T> : IMessageHandler where T : IMessage
{
private readonly Func<IMessageHandler<T>> handlerFactory;
public MessageHandlerAdapter(Func<IMessageHandler<T>> handlerFactory)
{
this.handlerFactory = handlerFactory;
}
public void Handle(IMessage message)
{
var handler = handlerFactory();
handler.Handle((T)message);
}
public Type MessageType
{
get { return typeof(T); }
}
}
We can now implement MessageReceiver this way:
public class MessageReceiver
{
private readonly IEnumerable<IMessageHandler> handlers;
public MessageReceiver(IEnumerable<IMessageHandler> handlers)
{
this.handlers = handlers;
}
public void ReceiveMessage(IMessage message)
{
var handler = this.handlers.Where(h => h.MessageType == message.GetType()).FirstOrDefault();
if (handler != null)
{
handler.Handle(message);
}
else
{
//Do something here, no handler found for message type
}
}
}
Now to test that our messages are processed properly, here is a small test:
[TestClass]
public class TestSelector
{
private IContainer container;
[TestMethod]
public void TestMethod()
{
var processor = container.Resolve<MessageReceiver>();
MessageA ma = new MessageA();
MessageB mb = new MessageB();
processor.ReceiveMessage(ma);
processor.ReceiveMessage(mb);
Assert.AreEqual(ma.Handled, true);
Assert.AreEqual(mb.Handled, true);
}
}
And we need to modify registration a bit, if opting for manual registration, we do as follow:
public TestSelector()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<MessageAHandler>().As<IMessageHandler<MessageA>>();
containerBuilder.RegisterType<MessageBHandler>().As<IMessageHandler<MessageB>>();
containerBuilder.RegisterType<MessageHandlerAdapter<MessageA>>().As<IMessageHandler>();
containerBuilder.RegisterType<MessageHandlerAdapter<MessageB>>().As<IMessageHandler>();
containerBuilder.RegisterType<MessageReceiver>();
this.container = containerBuilder.Build();
}
In here, we now need to register one handler and the relevant adapter.
It is also of course possible to perform assembly scan, but this requires a little bit more plumbing, since using:
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(x => x.IsAssignableFrom(typeof(IMessageHandler<IMessage>)))
.InstancePerDependency().AsImplementedInterfaces();
will not work
typeof(MessageAHandler).IsAssignableFrom(typeof(IMessageHandler<IMessage>))
will return false, since MessageAHandler implements IMessageHandler, not IMessageHandler
To do automatic discovery and registration, here is a snippet:
public TestSelector()
{
var containerBuilder = new ContainerBuilder();
Func<Type, Type> GetHandlerInterface = (t) => t.GetInterfaces()
.Where(iface => iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IMessageHandler<>)).FirstOrDefault();
var handlerTypes = typeof(IMessage).Assembly.GetTypes()
.Where(type => type.IsClass
&& !type.IsAbstract
&& GetHandlerInterface(type) != null);
foreach (Type handlerType in handlerTypes)
{
Type messageType = GetHandlerInterface(handlerType).GetGenericArguments()[0];
var genericHandler = typeof(MessageHandlerAdapter<>).MakeGenericType(messageType);
containerBuilder.RegisterType(handlerType).AsImplementedInterfaces();
containerBuilder.RegisterType(genericHandler).As<IMessageHandler>();
}
containerBuilder.RegisterType<MessageReceiver>();
this.container = containerBuilder.Build();
}
For anyone who is still looking for better solution for auto dispatching to appropriate message handlers registered, there is a nice implementation via MediatR.This is awesome library which can dispatch messages to appropriate registered handlers, and has capability to post messages to multiples handlers.
It is best suited for CQRS scenarios and also for Async Web API, refer CQRS using MediatR . There is a nice support when using DI container like Autofac and StructuredMap, Refer to wiki page of MediatR wiki for full details on DI support.
I'm creating a background task controller as following:
public class TaskController
{
private TaskBase task;
public TaskController(ITask task)
{
this.task = task;
}
public void DoSomething()
{
task.DoSomething();
}
}
ITask interface:
interface ITask
{
void DoSomething();
}
TaskBase abtract class:
public abtract class TaskBase : ITask
{
\\some common fields/properties/methods
public void DoSomething()
{
\\perform action here
}
}
Task implementation:
public class Task1 : TaskBase
{
public Task1(string arg, int arg1)
{
}
}
public class Task2 : TaskBase
{
public Task2(bool arg, double arg)
{
}
}
This is an example on how to use it:
public void DoTask(string arg, int arg1)
{
Task1 task = new Task1(arg, arg1);
TaskController controller = new TaskController(task);
controller.DoSomething();
}
As you can see, I'm using manual injection in this approach. Now I want to switch to using IoC like NInject but after done some research, there's 2 things still bug me.
1. How can I tell the binding which concrete task to use in particular context?
2. How to pass dynamic arguments (`arg` and `arg1` on above example) to `Bind<T>` method
Note:
Please leave some comment if you see my question deserve a downvote in order to help me avoid making mistake in the future
The problems you have are caused by your design. If you change your design, the problems will go away. There are a few things you should do:
Separate data and behavior; currently, your Tasks contain a DoSomething method, while they also contain the data they need to execute.
And related, inject runtime data into your components' constructors.
If you extract the data from the behavior, you'll get the following:
// Definition of the data of Task1
public class Task1Data
{
public string Arg;
public int Arg1;
}
// The behavior of Task1
public class Task1 : ITask<Task1Data> {
public void Handle(TTask1Data data) {
// here the behavior of this task.
}
}
Here every task implements the generic ITask<TTaskData> interface:
public interface ITask<TTaskData>
{
Handle(TTaskData data);
}
With this design in place, we can now use it as follows:
private ITask<Task1Data> task1;
public Consumer(ITask<Task1Data> task1) {
this.task1 = task1;
}
public void DoTask(string arg, int arg1)
{
task1.Handle(new Task1Data { Arg = arg, Arg1 = arg1 });
}
And we register our tasks as follows:
kernel.Bind<ITask<Task1Data>>().To<Task1>();
kernel.Bind<ITask<Task2Data>>().To<Task2>();
kernel.Bind<ITask<Task3Data>>().To<Task3>();
Although I'm not very experienced with Ninject, I'm sure there's a way to transform these registrations to a convenient single line.
This design has many advantages. For instance, it makes adding cross-cutting concerns much easier. For instance, you can create a generic decorator that wraps each task in a transaction as follows:
public class TransactionTaskDecorator<T> : ITask<T> {
private readonly ITask<T> decoratee;
public TransactionTaskDecorator(ITask<T> decoratee) {
this.decoratee = decoratee;
}
public void Handle(T data) {
using (var scope = new TransactionScope()) {
this.decoratee.Handle(data);
scope.Complete();
}
}
}
Such decorator can be applied without the consumer having to know anything about it, since it just depends on the ITask<T> interface.
You can also add a decorator that allows executing the tasks in a background thread:
public class BackgroundTaskDecorator<T> : ITask<T> {
private readonly Func<ITask<T>> decorateeFactory;
private readonly ILogger logger;
public TransactionTaskDecorator(Func<ITask<T>> decorateeFactory, ILogger logger) {
this.decorateeFactory = decorateeFactory;
this.logger = logger;
}
public void Handle(T data) {
Task.Factory.StartNew(() =>
{
try {
// We're running on a different thread, so we must create the task here.
var decoratee = this.decorateeFactory.Invoke();
decoratee.Handle(data);
} catch (Exception ex) {
this.logger.Log(ex);
}
}
}
}
You can learn more about this design here.
1) Use Named properties like so
public TaskController([Named("MyName")] ITask task)
Then in the NinjectModule
Bind<ITask>().To<Task1>().Named("MyName");
2) Think you can use the same method as above
https://github.com/ninject/ninject/wiki/Contextual-Binding