How to test a command- and event based system with Masstransit - c#

I have a command handler that invokes an operation on a domain object which in turn fires an event when the operation has been executed. I'd like to test that an event handler receives the event when the corresponding command has been sent (see below, some code omitted for brevity). The event handler (MyEventConsumer.Consume) is never invoked even though the event message is published on the bus (loopback bus in this case). Any ideas?
//Test
[TestFixture]
public class TestSendCommandReceiveEvent
{
[Given]
public void installation_of_infrastructure_objects()
{
container.Register(Component.For<MyEventConsumer>().UsingFactoryMethod(() => new MyEventConsumer(_received)));
container.Register(
Component.For<IServiceBus>()
.UsingFactoryMethod(() => ServiceBusFactory.New(x => { x.ReceiveFrom("loopback://localhost/mt_client"); x.Subscribe(conf => conf.LoadFrom(container)); })));
}
[When]
public void sending_a_command()
{
var LocalBus = container.Resolve<IServiceBus>();
LocalBus.Publish(new DoSomething(_aggregateId));
}
[Then]
public void corresponding_event_should_be_received_by_consumer()
{
_received.WaitOne(5000).ShouldBeTrue();
}
}
public class MyEventConsumer : Consumes<SomethingDone>.All
{
private readonly ManualResetEvent _received;
public MyEventConsumer(ManualResetEvent received)
{
_received = received;
}
public void Consume(SomethingDone message)
{
_received.Set();
}
}
//Command handler
public class DoSomethingCommandHandler : Consumes<DoSomething>.All where T:class
{
public void Consume(DoSomething message)
{
var ar = Repository.GetById<SomeAR>(message.ArId);
ar.DoSomething();
Repository.Save(ar, Guid.NewGuid(), null);
}
}
//Domain object
public class SomeDomainObject : AggregateBase
{
public void DoSomething()
{
RaiseEvent(new SomethingDone(Id, 1));
}
}

This passes for me:
// Copyright 2012 Henrik Feldt
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
using System;
using System.Threading;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Magnum.Extensions;
using Magnum.TestFramework;
using MassTransit;
using NUnit.Framework;
namespace ConsoleApplication11
{
[TestFixture]
public class TestSendCommandReceiveEvent
{
ManualResetEventSlim _received = new ManualResetEventSlim(false);
IWindsorContainer _container;
[Given]
public void installation_of_infrastructure_objects()
{
_container = new WindsorContainer();
_container.Register(
Component.For<IServiceBus>()
.UsingFactoryMethod(() => ServiceBusFactory.New(x =>
{
x.ReceiveFrom("loopback://localhost/mt_client");
x.Subscribe(conf =>
{
conf.Consumer(() => new MyEventConsumer(_received));
conf.Consumer(() => new MyCmdConsumer());
});
})));
when();
}
public void when()
{
var localBus = _container.Resolve<IServiceBus>();
// wait for startup
localBus.Endpoint.InboundTransport.Receive(c1 => c2 => { }, 1.Milliseconds());
localBus.Publish(new DoSomething());
}
[Then]
public void corresponding_event_should_be_received_by_consumer()
{
_received.Wait(5000).ShouldBeTrue();
}
}
[Serializable]
public class DoSomething
{
}
[Serializable]
public class SomethingDone
{
}
public class MyEventConsumer : Consumes<SomethingDone>.All
{
readonly ManualResetEventSlim _received;
public MyEventConsumer(ManualResetEventSlim received)
{
_received = received;
}
public void Consume(SomethingDone message)
{
_received.Set();
}
}
public class MyCmdConsumer : Consumes<DoSomething>.Context
{
public void Consume(IConsumeContext<DoSomething> ctx)
{
Console.WriteLine("consumed cmd");
ctx.Bus.Publish(new SomethingDone());
}
}
}

In my experience, there is a short period of time, right after creation of the bus instance, during which any published messages are lost. Must be some kind of async initialization going on.
Try adding a delay between container.Resolve<IServiceBus>() and LocalBus.Publish(new DoSomething(_aggregateId)).
Thread.Sleep did not work in my case, but a Console.ReadLine() surprisingly did!

Related

Why can Autofac not create this class that takes a non-generic implementation of a generic interface as a constructor parameter?

Using Autofac for injection.
Given
interface IStateMachine<TState, TTrigger> { }
class ConcreteStateMachine : IStateMachine<MachineState, Trigger> { }
builder.RegisterType<ConcreteStateMachine>().As<IStateMachine<MachineState, Trigger>>();
class Consumer { Consumer(IStateMachine<MachineState, Trigger> machine) { } }
Why does container.Resolve<Consumer>(); fail with this exception:
Unhandled exception.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating Consumer.
---> Autofac.Core.DependencyResolutionException: An exception was thrown while invoking the
constructor 'Void .ctor(IStateMachine`2[MachineState,Trigger])' on type 'Consumer'.
---> System.NullReferenceException: Object reference not set to an instance of an object.
Full code contrived example:
Requires nuget for Autofac and Stateless
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Autofac;
using Stateless;
public class Program
{
public static void Main()
{
var builder = new ContainerBuilder();
builder.RegisterType<Consumer>()
.InstancePerLifetimeScope();
builder.RegisterType<ConcreteStateMachine>()
.As<IStateMachine<MachineState, Trigger>>()
.InstancePerLifetimeScope();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var consumer = scope.Resolve<Consumer>();
consumer.TurnOn();
consumer.GetData();
consumer.TurnOff();
}
}
}
public class Consumer
{
protected IStateMachine<MachineState, Trigger> Machine;
public Consumer(IStateMachine<MachineState, Trigger> machine)
{
Machine = machine;
Machine.Register(MachineState.Off, () => Debug.WriteLine("State Off"));
Machine.Register(MachineState.On, () => Debug.WriteLine("State On"));
}
public List<string> GetData()
{
if (!Machine.IsInState(MachineState.On)) throw new InvalidOperationException("Can't GetData when machine is off!");
Debug.WriteLine("Getting Data");
return new List<String> {"Data", "Data", "Data"};
}
public void TurnOn()
{
Machine.Fire(Trigger.TurnOn);
}
public void TurnOff()
{
Machine.Fire(Trigger.TurnOff);
}
}
public class ConcreteStateMachine : IStateMachine<MachineState, Trigger>
{
protected StateMachine<MachineState, Trigger> Machine;
public MachineState CurrentState { get; set; }
public void Fire(Trigger trigger)
{
Machine.Fire(trigger);
}
public async Task FireAsync(Trigger trigger)
{
await Machine.FireAsync(trigger);
}
public bool IsInState(MachineState state)
{
return Machine.IsInState(state);
}
public void Register(MachineState state, Action callback)
{
Machine.Configure(state)
.OnEntry(() => callback.Invoke());
}
public void RegisterAsync(MachineState state, Func<Task> callback)
{
Machine.Configure(state)
.OnEntryAsync(async () => await callback.Invoke());
}
public void Start()
{
ConfigureMachine();
Machine.Activate();
}
protected void ConfigureMachine()
{
Machine = new StateMachine<MachineState, Trigger>(MachineState.Off);
Machine.Configure(MachineState.Off)
.Permit(Trigger.TurnOn, MachineState.On);
Machine.Configure(MachineState.On)
.Permit(Trigger.TurnOff, MachineState.Off);
}
}
public interface IStateMachine<TState, TTrigger>
{
TState CurrentState { get; set; }
bool IsInState(TState state);
void Fire(TTrigger trigger);
Task FireAsync(TTrigger trigger);
void Start();
void Register(TState state, Action callback);
void RegisterAsync(TState state, Func<Task> callback);
}
public enum MachineState
{
Off,
On
}
public enum Trigger
{
TurnOff,
TurnOn
}
I've tried to find an example of this type of injection using Autofac and I can't find anyone else who's had a similar problem.
The container will resolve an instance of ConcreteStateMachine directly.
As in container.Resolve<IStateMachine<MachineState, Trigger>>() correctly provides a ConcreteStateMachine, but Autofac doesn't seem to know how to send an instance to Consumer.
Machine field of ConcreteStateMachine is not initialized and is null.
So it fails with NullReferenceException in:
public void Register(MachineState state, Action callback)
{
Machine.Configure(state)
.OnEntry(() => callback.Invoke());
}
Just a case of introducing a bug while creating the contrived example...
I never called Machine.Start() in the constructor of Consumer, which instantiates the internal StateMachine, prior to calling Register, which invokes a method on the underlying StateMachine. This caused an exception to be thrown while creating Consumer and was not in fact an Autofac configuration issue.
Sorry!

C# dependency injection has duplicate singletons

I'm working on a Xamarin.Forms app and for some reason there are singletons that are created multiple times. This does not happen every time however and it seems to be at random. My dependency injection setup happens in the App.xaml.cs
using System;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.Extensions.DependencyInjection;
using Peripass.Mobile.Framework;
using Peripass.Mobile.Framework.DependencyInterfaces;
using Peripass.YardAssetManagementApp.Data;
using Peripass.YardAssetManagementApp.Data.FileSystem;
using Peripass.YardAssetManagementApp.Data.LocalDataServices;
using Peripass.YardAssetManagementApp.Data.Queues;
using Peripass.YardAssetManagementApp.Data.RemoteDataServices;
using Peripass.YardAssetManagementApp.Data.Syncing;
using Peripass.YardAssetManagementApp.Data.Syncing.QueueItemSyncActions;
using Peripass.YardAssetManagementApp.Device;
using Peripass.YardAssetManagementApp.MVVM;
using Peripass.YardAssetManagementApp.MVVM.Pages;
using Peripass.YardAssetManagementApp.PushNotifications;
using Peripass.YardAssetManagementApp.Services;
using Peripass.YardAssetManagementApp.StepsWizard;
using Peripass.YardAssetManagementApp.UI.Views;
using Peripass.YardAssetManagementApp.UserAuthentication;
using Peripass.YardAssetManagementApp.ViewModels;
using Xamarin.Forms;
using Application = Xamarin.Forms.Application;
namespace Peripass.YardAssetManagementApp {
public partial class App : Application, IApp {
private string _startedByAndroidNotificationMessage;
public App() {
ConfigureServices();
InitializeComponent();
AppCenter.Start($"android={Configuration.Configuration.ApplicationInsightsKeyAndroid};", typeof(Analytics), typeof(Crashes));
MainPage = ServiceProvider.GetRequiredService<MainPage>();
}
public static IServiceCollection Services { get; } = new ServiceCollection();
public IServiceProvider ServiceProvider { get; set; }
private void ConfigureServices() {
Services.AddSingleton<ILifecycleHooksService, LifecycleHooksService>();
Services.AddTransient<INetworkInformationProvider, NetworkInformationProvider>();
RegisterLocalDataServices();
RegisterRemoteDataServices();
RegisterSyncLogic();
RegisterServices();
RegisterViews();
Services.AddSingleton<IUserManager, UserManager>();
Services.AddSingleton<HttpClientProvider>();
Services.AddSingleton<ILogger, Logger>();
Services.AddSingleton<INavigationService, NavigationService>();
Services.AddSingleton<StepsWizardManager>();
Services.AddSingleton<MainPage>();
Services.AddSingleton(DependencyService.Get<ILocalFileSystem>());
Services.AddSingleton<IRestServiceHelper, RestServiceHelper>();
Services.AddSingleton<IPushNotificationsService, PushNotificationsService>();
ServiceProvider = Services.BuildServiceProvider();
}
public void RegisterServices() {
Services.AddTransient<ITaskService, TaskService>();
Services.AddTransient<ILocationService, LocationService>();
Services.AddTransient<ILocalizationService, LocalizationService>();
Services.AddTransient<IDiagnosticsService, DiagnosticsService>();
}
public void RegisterLocalDataServices() {
Services.AddSingleton<ILocalTaskDataService, LocalTaskDataService>();
Services.AddSingleton<ILocalLocationDataService, LocalLocationDataService>();
Services.AddSingleton<ILocalUserDataService, LocalUserDataService>();
Services.AddSingleton<ILocalPushNotificationDataService, LocalPushNotificationDataService>();
Services.AddSingleton<ILocalPictureDataService, LocalPictureDataService>();
Services.AddSingleton<ISafeFileSystem, SafeFileSystem>();
Services.AddSingleton<ILocalLocalizationDataService, LocalLocalizationDataService>();
}
public void RegisterRemoteDataServices() {
Services.AddSingleton<IRemoteTaskDataService, RemoteTaskDataService>();
Services.AddSingleton<IRemoteLocationDataService, RemoteLocationDataService>();
Services.AddSingleton<IRemoteUserDataService, RemoteUserDataService>();
Services.AddSingleton<IRemoteFileDataService, RemoteFileDataService>();
Services.AddSingleton<IRemoteLocalizationDataService, RemoteLocalizationDataService>();
}
public void RegisterSyncLogic() {
Services.AddSingleton<IToMobileTasksSyncer, ToMobileTasksSyncer>();
Services.AddSingleton<IQueue, IncomingHighPriorityQueue>();
Services.AddSingleton<IQueue, IncomingLowPriorityQueue>();
Services.AddSingleton<IQueue, OutgoingHighPriorityQueue>();
Services.AddSingleton<IQueue, OutgoingLowPriorityQueue>();
Services.AddSingleton<IQueue, PictureSyncLowPriorityQueue>();
Services.AddSingleton<IQueue, PictureSyncHighPriorityQueue>();
Services.AddSingleton<IQueueOrchestrator, IncomingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, OutgoingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, PictureQueueOrchestrator>();
Services.AddSingleton<IQueueProcessor, IncomingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, OutgoingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, PictureQueueProcessor>();
Services.AddSingleton<IQueueItemSyncAction, FetchTaskDetailSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, OutgoingStepValueUpdateSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, OutgoingTaskStatusChangedSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, SyncPictureAction>();
Services.AddSingleton<IFileIntegrityHelper, FileIntegrityHelper>();
}
public void RegisterViews() {
Services.AddTransient<LoginViewContent>();
Services.AddSingleton<LoginViewModel>();
Services.AddTransient<UserInfoViewContent>();
Services.AddSingleton<UserInfoViewModel>();
Services.AddTransient<MainTaskListViewContent>();
Services.AddSingleton<MainTaskListViewModel>();
Services.AddTransient<TaskDetailViewContent>();
Services.AddSingleton<TaskDetailViewModel>();
Services.AddTransient<IntegerFieldTaskStepViewContent>();
Services.AddSingleton<IntegerFieldTaskStepViewModel>();
Services.AddTransient<TextAreaTaskStepViewContent>();
Services.AddSingleton<TextAreaTaskStepViewModel>();
Services.AddTransient<DropDownTaskStepViewContent>();
Services.AddSingleton<DropDownTaskStepViewModel>();
Services.AddTransient<MoveToLocationSummaryViewContent>();
Services.AddSingleton<MoveToLocationSummaryViewModel>();
Services.AddTransient<TakePictureStepViewContent>();
Services.AddSingleton<TakePictureStepViewModel>();
Services.AddTransient<MoveToLocationStepViewContent>();
Services.AddSingleton<MoveToLocationStepViewModel>();
Services.AddTransient<TaskCompletedViewContent>();
Services.AddSingleton<TaskCompletedViewModel>();
Services.AddTransient<LoggedOutViewContent>();
Services.AddSingleton<LoggedOutViewModel>();
Services.AddTransient<TextFieldTaskStepViewContent>();
Services.AddSingleton<TextFieldTaskStepViewModel>();
Services.AddTransient<DecimalFieldTaskStepViewContent>();
Services.AddSingleton<DecimalFieldTaskStepViewModel>();
Services.AddTransient<GenericExceptionPageViewContent>();
Services.AddSingleton<GenericExceptionPageViewModel>();
Services.AddTransient<CleanupBeforeLogoutPageViewContent>();
Services.AddSingleton<CleanupBeforeLogoutPageViewModel>();
Services.AddTransient<SelectLanguageViewContent>();
Services.AddSingleton<SelectLanguageViewModel>();
}
protected override async void OnStart() {
await ServiceProvider.GetRequiredService<ILifecycleHooksService>().OnStart();
}
protected override void OnSleep() { }
protected override async void OnResume() {
await ServiceProvider.GetRequiredService<ILifecycleHooksService>().OnResume();
}
public void StartedByTapOnAndroidNotification(string message) {
_startedByAndroidNotificationMessage = message;
}
}
}
The singletons that are created multiple times are these ones. It could be that other singletons are also created muiltiple times but these I'm sure of.
Services.AddSingleton<IQueueOrchestrator, IncomingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, OutgoingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, PictureQueueOrchestrator>();
Services.AddSingleton<IQueueProcessor, IncomingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, OutgoingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, PictureQueueProcessor>();
I see that the OnStart method is sometimes called multiple times creating multiple instances of these singletons when resolving the dependencies of the ILifecycleHooksService. This is the code in that LifeCycleHooksService:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Peripass.YardAssetManagementApp.Data;
using Peripass.YardAssetManagementApp.Data.LocalDataServices;
using Peripass.YardAssetManagementApp.Data.Syncing;
using Peripass.YardAssetManagementApp.MVVM;
using Peripass.YardAssetManagementApp.PushNotifications;
using Peripass.YardAssetManagementApp.ViewModels;
namespace Peripass.YardAssetManagementApp {
public interface ILifecycleHooksService {
Task OnStart();
Task OnResume();
}
public class LifecycleHooksService : ILifecycleHooksService {
private readonly INavigationService _navigationService;
private readonly ILocalUserDataService _localUserDataService;
private readonly ILocalTaskDataService _localTaskDataService;
private readonly ILocalLocationDataService _localLocationDataService;
private readonly ILocalPushNotificationDataService _localPushNotificationDataService;
private readonly ILocalPictureDataService _localPictureDataService;
private readonly IToMobileTasksSyncer _toMobileTasksSyncer;
private readonly IEnumerable<IQueueProcessor> _queueProcessors;
private readonly IPushNotificationsService _pushNotificationsService;
private readonly ILocalLocalizationDataService _localLocalizationDataService;
public LifecycleHooksService(INavigationService navigationService,
ILocalUserDataService localUserDataService,
IToMobileTasksSyncer toMobileTasksSyncer,
IEnumerable<IQueueProcessor> queueProcessors,
ILocalTaskDataService localTaskDataService,
ILocalLocationDataService localLocationDataService,
ILocalPushNotificationDataService localPushNotificationDataService,
IPushNotificationsService pushNotificationsService,
ILocalPictureDataService localPictureDataService, ILocalLocalizationDataService localLocalizationDataService) {
_navigationService = navigationService;
_localUserDataService = localUserDataService;
_toMobileTasksSyncer = toMobileTasksSyncer;
_queueProcessors = queueProcessors;
_localTaskDataService = localTaskDataService;
_localLocationDataService = localLocationDataService;
_localPushNotificationDataService = localPushNotificationDataService;
_pushNotificationsService = pushNotificationsService;
_localPictureDataService = localPictureDataService;
_localLocalizationDataService = localLocalizationDataService;
}
public async Task OnStart() {
await ReloadData();
await CreateDeviceIdOnFirstStartAsync();
var currentUser = _localUserDataService.GetCurrentUser();
if (currentUser.IsLoggedIn) {
Configuration.Configuration.SetEnvironment(currentUser.Environment);
await _navigationService.NavigateToViewModelAsync<MainTaskListViewModel>(true);
try {
await _toMobileTasksSyncer.SyncAllMetaData();
}
catch {
// ignored
}
}
else {
await _navigationService.NavigateToViewModelAsync<LoginViewModel>();
}
foreach (var queueProcessor in _queueProcessors) {
_ = Task.Run(async () => await queueProcessor.StartPeriodicProcessing());
}
}
public async Task OnResume() {
try {
await _toMobileTasksSyncer.SyncAllTasks(true);
}
catch {
// ignored
}
}
public async Task CreateDeviceIdOnFirstStartAsync() {
var currentPushNotificationInfo = await _localPushNotificationDataService.GetCurrentPushNotificationInfo();
if (string.IsNullOrEmpty(currentPushNotificationInfo.DeviceIdTag)) {
await _localPushNotificationDataService.SetDeviceId(Guid.NewGuid().ToString());
}
}
private async Task ReloadData() {
await _localLocalizationDataService.ReloadData();
await _localUserDataService.ReloadData();
await _localTaskDataService.ReloadData();
await _localLocationDataService.ReloadData();
await _localPictureDataService.ReloadData();
await _pushNotificationsService.ProcessReceivedMessages();
}
}
}
In this class I inject an I Enumerable of IQueueProcessors. These queueProcessors inject themselves the IQueueOrchestrators. If you need more information, please ask me anything.
I found out what was causing it. The cause is android related and is described in this stack overflow post: description of what causes this
The solution that worked for me came from this post about the same topic: solution for me
Basically the solution that worked for me was adding this to OnCreate:
// Possible work around for market launches. See https://issuetracker.google.com/issues/36907463
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
if (!isTaskRoot()) {
final Intent intent = getIntent();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) {
Log.w(LOG_TAG, "Main Activity is not the root. Finishing Main Activity instead of launching.");
finish();
return;
}
}

How do I get CreateUsingInMemory() to work with MassTransit?

Here's my entire code. I think the test should pass, but it fails. I've (unsuccessfully) tried using some of the overloads to Consumer.
using MassTransit;
using NUnit.Framework;
using System.Threading.Tasks;
namespace MassTransitTests
{
public class Message
{
}
public class MessageConsumer : IConsumer<Message>
{
public static int ConsumedCount
{
get;
private set;
}
public Task Consume(ConsumeContext<Message> context)
{
ConsumedCount++;
return Task.FromResult(0);
}
}
[TestFixture]
public class MassTransitTest
{
[Test]
public async Task BasicTestAsync()
{
// Arrange
var control = Bus.Factory.CreateUsingInMemory(configure =>
{
configure.ReceiveEndpoint("myQueue", endpoint =>
{
endpoint.Consumer<MessageConsumer>();
});
});
// Act
using (var handle = control.Start())
{
await control.Publish(new Message());
await control.Publish(new Message());
}
// Assert
Assert.That(MessageConsumer.ConsumedCount, Is.EqualTo(2));
}
}
}
Their documentation shows this, which is what I'm doing:
var busControl = Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ReceiveEndpoint("queue_name", ep =>
{
//configure the endpoint
})
});
What am I doing wrong/what I do need to change in my Arrange/Act to get my Assert to work?
After digging through their tests, I found what I was missing:
[1] You need* to await BusHandle.Ready, which I wasn't doing. *(The test works without this - at least the first time I ran it, but that may just be a race condition working in my favor....)
[2] The calls to Publish apparently complete whenever the bus has received the message I'm guessing - not when the handlers/consumers of the message have completed their work. Therefore you need to notify the calling code that the handlers have finished if that's what you're testing. Here's one way to do this - use TaskCompletionSource<T> (similar to what I found in their codebase). Obviously I may not have been perfect in my thread-safety and my lock usage is a bit sledge-hammer-esque, but this illustrates the point:
using MassTransit;
using NUnit.Framework;
using System.Threading.Tasks;
namespace MassTransitTests
{
public class Message
{
}
public class MessageConsumer : IConsumer<Message>
{
public static int TargetConsumedCount
{
get { return _targetConsumedCount; }
set
{
lock (_lock)
{
_targetConsumedCount = value;
CheckTargetReached();
}
}
}
private static void CheckTargetReached()
{
if (_consumedCount >= TargetConsumedCount)
{
_targetReached.SetResult(true);
}
}
public static Task<bool> TargetReached { get; private set; }
private static int _consumedCount;
private static int _targetConsumedCount;
private static TaskCompletionSource<bool> _targetReached;
private static object _lock;
static MessageConsumer()
{
_lock = new object();
_targetReached = new TaskCompletionSource<bool>();
TargetReached = _targetReached.Task;
}
public Task Consume(ConsumeContext<Message> context)
{
lock (_lock)
{
_consumedCount++;
CheckTargetReached();
}
return Task.FromResult(0);
}
}
[TestFixture]
public class MassTransitTest
{
[Test]
public async Task BasicTestAsync()
{
// Arrange
var control = Bus.Factory.CreateUsingInMemory(configure =>
{
configure.ReceiveEndpoint("myQueue", endpoint =>
{
endpoint.Consumer<MessageConsumer>();
});
});
using (var handle = control.Start())
{
await handle.Ready; // [1]
// Act
await control.Publish(new Message());
await control.Publish(new Message());
// Assert
MessageConsumer.TargetConsumedCount = 2;
await MessageConsumer.TargetReached; // [2]
}
}
}
}

Single Message Handler Factory using Autofac

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.

Using webbackgrounder nuget in MVC to run background task for long time

I need to implement a task in background so what is my task? I have a table that stores the rent amount of each customers, so I need to calculate the rent price in each month after a specific datetimeackf so I googled it and I found a piece of code that (it is nuget called webbackgrounder) I added it to my solution and it gives me this part of code to handle my task:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace WebBackgrounder.DemoWeb
{
public class SampleJob : Job
{
public SampleJob(TimeSpan interval, TimeSpan timeout)
: base("Sample Job", interval, timeout)
{
}
public override Task Execute()
{
return new Task(() => Thread.Sleep(3000));
}
}
}
I want to know how can I program my task ?
More details : Here
I found this article but in fact I don't know can I use this method for longtime ??
Best regards .
any ideas will be appreciated.
You need to also add in a class to the App_Start folder of your application that will start the Job and manage it's lifetime. You can see an example here... https://github.com/NuGet/WebBackgrounder/tree/master/src/WebBackgrounder.DemoWeb
Here is the code from the demo app
using System;
using Elmah;
using WebBackgrounder.Jobs;
[assembly: WebActivator.PostApplicationStartMethod(typeof(WebBackgrounder.DemoWeb.App_Start.WebBackgrounderSetup), "Start")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(WebBackgrounder.DemoWeb.App_Start.WebBackgrounderSetup), "Shutdown")]
namespace WebBackgrounder.DemoWeb.App_Start
{
public static class WebBackgrounderSetup
{
static readonly JobManager _jobManager = CreateJobWorkersManager();
public static void Start()
{
_jobManager.Start();
}
public static void Shutdown()
{
_jobManager.Dispose();
}
private static JobManager CreateJobWorkersManager()
{
var jobs = new IJob[]
{
new SampleJob(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)),
/* new ExceptionJob(TimeSpan.FromSeconds(15)), */
new WorkItemCleanupJob(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5), new WorkItemsContext())
};
var coordinator = new WebFarmJobCoordinator(new EntityWorkItemRepository(() => new WorkItemsContext()));
var manager = new JobManager(jobs, coordinator);
manager.Fail(ex => Elmah.ErrorLog.GetDefault(null).Log(new Error(ex)));
return manager;
}
}
}
However I found it simpler to just use the parts of Webbackgrounder that I needed as follows. Place this class in the App_Start folder
using System;
using BombaySapphireCds.Jobs;
using Elmah;
[assembly: WebActivator.PostApplicationStartMethod(typeof(BombaySapphireCds.App_Start.PodMonitorConfig), "Start")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(BombaySapphireCds.App_Start.PodMonitorConfig), "Shutdown")]
namespace BombaySapphireCds.App_Start
{
public static class PodMonitorConfig
{
private static PodMonitorJob m_job;
public static void Start()
{
m_job = new PodMonitorJob(TimeSpan.FromSeconds(20));
}
public static void Shutdown()
{
m_job.Dispose();
}
}
}
and the class to do the actual work... (put this anywhere you like)
using System;
using System.Threading;
using System.Threading.Tasks;
namespace BombaySapphireCds.Jobs
{
public class PodMonitorJob : IDisposable
{
private CancellationTokenSource m_cancel;
private Task m_task;
private TimeSpan m_interval;
private bool m_running;
public PodMonitorJob(TimeSpan interval)
{
m_interval = interval;
m_running = true;
m_cancel = new CancellationTokenSource();
m_task = Task.Run(() => TaskLoop(), m_cancel.Token);
}
private void TaskLoop()
{
while (m_running)
{
//
// Do monitoring work here.
//
Thread.Sleep(m_interval);
}
}
public void Dispose()
{
m_running = false;
if (m_cancel != null)
{
try
{
m_cancel.Cancel();
m_cancel.Dispose();
}
catch
{
}
finally
{
m_cancel = null;
}
}
}
}
}
This has become the new standard for background task execution on the web. It's a NuGet package and it's called HangFire - https://github.com/HangfireIO/Hangfire. The tasks persist even beyond apppool recycling.

Categories