I created an API in .net core with C# that has a collection of elements. That collection has a delegate that is triggered when a new element is added (is working). The problem is that I can't make it work unless I subscribe to the delegate from the controller.
Current scenario:
[HttpPost("welcome")]
public IActionResult Welcome([FromBody] MyClient newClient)
{
ClientsDataStore.Current.clients.Push(newClient);
return Ok(newClient);
}
I created the class MyClass:
public class MyClass
{
ILogger<MyClass> logger;
public MyClass(ILogger<MyClass> logger)
{
this.logger = logger;
ClientsDataStore.Current.clients.ElementAdded += ListenClients;
}
public async void ListenClients(object sender, MyClient e)
{
logger.LogDebug($"New client added");
if (e.VirtualMachine)
{
await ProcessVMAsync(e);
}
else
{
await ProcessDesktopAsync(e);
}
}
}
I tried to register the class in public void ConfigureServices(IServiceCollection services) like this:
services.AddTransient<MyClass>();
With that scenario, the delegate is never triggered, but if I use:
[HttpPost("welcome")]
public IActionResult Welcome([FromBody] MyClient newClient)
{
ClientsDataStore.Current.clients.ElementAdded += ListenClients;
ClientsDataStore.Current.clients.Push(newClient);
return Ok(newClient);
}
Then the event is triggered.
I would like to know what I am missing here and how could I accomplished this.
UPDATE:
I changed the approach of the solution. I am going to have a background service checking every 10 seconds if the collection has a new element.
Related
im trying to define two classes, the first one listens a Rabbitmq queue and invoke the event for the second one.
I inject bow classes like scope on my Program.cs (similar for Startup.cs on .net core):
builder.Services.AddScoped<IEventListen<NotificationDTO>, EventListenBase<NotificationDTO>>();
builder.Services.AddScoped<IEventHandler<NotificationDTO>, NotificationEventHandler>();
EventListenBase has the StartListen method and i need when starts the Api, the logic for StartListen begins (his constructor starts), and the logic for event on NotificationEventHandler begins too.
How i do that?
EventListenBase:
public class EventListenBase<T> : IEventListen<T>
where T: class
{
...
public event EventHandler<T> HandleMessage;
public EventListenBase(IEvent<T> #event)
{
...
this.StartListen();
}
public async Task StartListen()
{
var consumer = new AsyncEventingBasicConsumer(_event.GetModel());
consumer.Received += async (ch, ea) =>
{
var body = ea.Body.ToArray();
var text = System.Text.Encoding.UTF8.GetString(body);
var objectValue = JsonConvert.DeserializeObject<T>(text);
//Invoke event to dependencies
HandleMessage?.Invoke(this, objectValue);
...
};
...
}
}
NotificationEventHandler:
public class NotificationEventHandler : IEventHandler<NotificationDTO>
{
public NotificationEventHandler(IEventListen<NotificationDTO> eventHandler)
{
eventHandler.HandleMessage += EventHandler_HandleMessage;
}
private async void EventHandler_HandleMessage(object? sender, NotificationDTO e)
{
await ConcreteHandleMessage(e);
}
public async Task ConcreteHandleMessage(NotificationDTO message)
{
Console.WriteLine("Do something...");
}
}
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?
I created a notification Event that I need to trigger within my post API after its completion.
The event Args are:
public class OrderCreationEvents : EventArgs
{
public string PickUpLocation { get; set; }
public string CustumerId { get; set; }
public OrderCreationEvents(string _pickUpLocation, string _custumerId)
{
PickUpLocation = _pickUpLocation;
CustumerId = _custumerId;
}
}
The notification Service interface is:
public interface IOrderCreatedService
{
void OnOrderCreation(object sender, OrderCreationEvents args);
}
The Interface's implementation is:
public class OrderNotificationService : IOrderCreatedService
{
private readonly IUnitOfWork _unitOfWork;
public OrderNotificationService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async void OnOrderCreation(object sender, OrderCreationEvents args)
{
var note = new Notification { CustomerId = args.CustumerId, PickUpLocation = args.PickUpLocation };
await _unitOfWork.Notifications.Insert(note);
await _unitOfWork.Save();
}
}
Finally, when I create the post endpoint, I need to trigger this event
[HttpPost]
public async Task<IActionResult> Create(OrderIDTO orderIDTO)
{
var order = new Order {...};
await _unitOfWork.Orders.Insert(order);
//Calling the Notification Service
var note = new OrderNotificationService(_unitOfWork);
return RedirectToAction("index");
}
How do I trigger the OnOrderCreation mehtod inside the Create endpoint?
You also can follow Notification with MediatR which is really easy to use and understand.
This Link Can help to implement MediatR notification in your controller.
but, if you want to use MediatR notification in your scenario follow this:
public class OrderNotification : IAsyncNotification
{
public string PickUpLocation { get; set; }
public string CustumerId { get; set; }
}
public class OrderNotificationService : IAsyncNotificationHandler<OrderNotification>
{
private readonly IUnitOfWork _unitOfWork;
public OrderNotificationService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task Handle(OrderNotification notification)
{
var note = new Notification { CustomerId = notification.CustumerId, PickUpLocation = notification.PickUpLocation };
await _unitOfWork.Notifications.Insert(note);
await _unitOfWork.Save();
}
}
and your controller :
private IMediator _mediator;
public constructor(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Create(OrderIDTO orderIDTO)
{
var order = new Order { ...};
await _unitOfWork.Orders.Insert(order);
var note = new OrderNotification
{
CustomerId = order.CustomerId,
PickUpLocation = order.PickUpLocation
};
await _mediator.PublishAsync(note);
return RedirectToAction("index");
}
Remember before this, you need to install and setup the packages:
Assuming you have created an ASP.Net Core project in Visual Studio, the next step is installing the following NuGet packages.
MediatR
MediatR.Extensions.Microsoft.DependencyInjection
To do that, you can either use the NuGet Package Manager or the NuGet Package Manager Console.
now Configure MediatR in ASP.Net Core
Once the two packages mentioned in the earlier section have been successfully installed in your project, the next step is to configure MediatR in the Startup class. To do this, you should write the following code in the ConfigureServices method. Note that the ConfigureServices method is used to add services at runtime to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMediatR(typeof(Startup));
services.AddMvc().SetCompatibilityVersion
(CompatibilityVersion.Version_2_2);
}
I not 100% sure if i got what you want. but i think you do not want to use events. simply you can call the OnOrderCreation method like this:
var note = new OrderNotificationService(_unitOfWork);
await note.OnOrderCreation(this, new OrderCreationEvents {.....})
As there is no RelayCommandAsync (at least not that I know of), how to test this scenario. For example:
public RelayCommand LoadJobCommand
{
get
{
return this.loadJobCommand ?? (
this.loadJobCommand =
new RelayCommand(
this.ExecuteLoadJobCommandAsync));
}
}
private async void ExecuteLoadJobCommandAsync()
{
await GetData(...);
}
Test:
vm.LoadJobCommand.Execute()
Assert.IsTrue(vm.Jobs.Count > 0)
It really depends on what you are trying to test:
Test that the RelayCommand is properly hooked up and calls your async
method?
or
Test that the Async Method logic is correct?
1. Testing the RelayCommand trigger
1.a Using External Dependencies to verify
From my personal experience the easiest way to test that the trigger is wired up correctly to execute the command and then test that your class has interacted with another external class somewhere as expected. E.g.
private async void ExecuteLoadJobCommandAsync()
{
await GetData(...);
}
private async void GetData(...)
{
var data = await _repo.GetData();
Jobs.Add(data);
}
Its fairly easy to test that your repo gets called.
public void TestUsingExternalDependency()
{
_repo.Setup(r => r.GetData())
.Returns(Task.Run(() => 5))
.Verifiable();
_vm.LoadJobCommand.Execute(null);
_repo.VerifyAll();
}
I sometimes even do this, so that it doesn't try to process everything:
[Test]
public void TestUsingExternalDependency()
{
_repo.Setup(r => r.GetData())
.Returns(() => { throw new Exception("TEST"); })
.Verifiable();
try
{
_vm.LoadJobCommand.Execute(null);
}
catch (Exception e)
{
e.Message.Should().Be("TEST");
}
_repo.VerifyAll();
}
1.b Using a Scheduler
Another option is to use a scheduler, and schedule tasks using that.
public interface IScheduler
{
void Execute(Action action);
}
// Injected when not under test
public class ThreadPoolScheduler : IScheduler
{
public void Execute(Action action)
{
Task.Run(action);
}
}
// Used for testing
public class ImmediateScheduler : IScheduler
{
public void Execute(Action action)
{
action();
}
}
Then in your ViewModel
public ViewModelUnderTest(IRepository repo, IScheduler scheduler)
{
_repo = repo;
_scheduler = scheduler;
LoadJobCommand = new RelayCommand(ExecuteLoadJobCommandAsync);
}
private void ExecuteLoadJobCommandAsync()
{
_scheduler.Execute(GetData);
}
private void GetData()
{
var a = _repo.GetData().Result;
Jobs.Add(a);
}
And your test
[Test]
public void TestUsingScheduler()
{
_repo.Setup(r => r.GetData()).Returns(Task.Run(() => 2));
_vm = new ViewModelUnderTest(_repo.Object, new ImmediateScheduler());
_vm.LoadJobCommand.Execute(null);
_vm.Jobs.Should().NotBeEmpty();
}
2. Testing the GetData Logic
If you are looking to test get GetData() logic or even the ExecuteLoadJobCommandAsync() logic. Then you should definitely make the method you want to test, as Internal, and mark your assmebly as InternalsVisibleTo so that you can call those methods directly from your test class.
Why don't you cover GetData(...) method with tests? I don't see any sense in testing relay commands
I was not using async/await but I have run in to a similar problem in the past. The situation I was in is the method called a Task.Run( inside of itself and the unit test was verifying that the ViewModel was calling the service with the correct number of times with the correct parameters.
The way we solved this was we had our Mock of the service that was being called use a ManualResetEventSlim, then the unit test waited for that reset event to be called before proceeding.
[TestMethod]
public void EXAMPLE()
{
using (var container = new UnityAutoMoqContainer())
{
//(SNIP)
var serviceMock = container.GetMock<ITreatmentPlanService>();
var resetEvent = new ManualResetEventSlim();
serviceMock.Setup(x=>x.GetSinglePatientViewTable(dateWindow, currentPatient, false))
.Returns(() =>
{
resetEvent.Set();
return new ObservableCollection<SinglePatientViewDataRow>();
});
var viewModel = container.Resolve<SinglePatientViewModel>();
//(SNIP)
viewModel.PatientsHadTPClosed(guids, Guid.NewGuid());
waited = resetEvent.Wait(timeout);
if(!waited)
Assert.Fail("GetSinglePatientViewTable was not called within the timeout of {0} ms", timeout);
//(SNIP)
serviceMock.Verify(x => x.GetSinglePatientViewTable(dateWindow, currentPatient, false), Times.Once);
}
}
If this approach works or not for you all depends on what your unit test is actually testing. Because you check Assert.IsTrue(vm.Jobs.Count > 0) it looks like you have extra logic that is being done after the await GetData(...); call, so this might not be applicable for your current problem. However, this may be helpful for other unit tests you need to write for your view model.
I have a service that exposes async operation via event driven async pattern.
public interface IService
{
void DoAsync(int param);
event DoCompleted;
}
There is another class that depends on IService service object
public class Foo
{
private IService _service;
public EventHandler CalculationComplete;
public void Foo(IService service) {_service = service};
public int Calculated;
public void CalculateAsync(int param)
{
//Invoke _service.DoAsync(param)
//(...)
}
}
Basically after calling foo.CalculateAsyc CalculationComplete should notify consumer of calc completion.
The question is how to mock IService when unit testing Foo ? I am using Moq. More specifically how to make unittest wait for CalculationComplete event and react accordingly?
Hard to know what you are trying to test here, so I can't give you a 100% accurate sample. Your sample code seems to be missing quite a few details... I filled some of the missing bits in, but there are more questions.
In any case, a method I use for waiting on events is a semaphore. I like to use AutoResetEvent for this in simple occasions like this.
public class Foo
{
private IService _service;
public EventHandler CalculationComplete;
public Foo(IService service)
{
_service = service;
_service.DoCompleted += (o,e) =>
{
Calculated = e.Result;
if(CalculationComplete != null) { CalculationComplete(this, new EventArgs()); }
};
}
public int Calculated;
public void CalculateAsync(int param)
{
_service.DoAsync(param);
}
}
public interface IService
{
void DoAsync(int param);
event EventHandler<DoResultEventArgs> DoCompleted;
}
public class DoResultEventArgs : EventArgs
{
public int Result { get; set; }
}
[TestMethod]
public void CalculateAsync_CallsService_CalculatedIsPopulated()
{
//Arrange
Mock<IService> sMock = new Mock<IService>();
sMock.Setup(s => s.DoAsync(It.IsAny<int>()))
.Raises(s => s.DoCompleted += null, new DoResultEventArgs() { Result = 324 });
Foo foo = new Foo(sMock.Object);
AutoResetEvent waitHandle = new AutoResetEvent(false);
foo.CalculationComplete += (o,e) => waitHandle.Set();
//Act
foo.CalculateAsync(12);
waitHandle.WaitOne();
//Assert
Assert.IsEqual(foo.Calculated, 324);
}
Without more information, this is the best I can do. I hope it was what you were looking for.