I have configured Quartz in my .Net program and I have a strange issue happening that I need help with.
What happens is that, if I put the ActuationService with the constructor commented the Quartz implementation works showing the "Hello message. Otherwise, if I have the ActuationService with the constructor method (like in the code below) the message "Hello" does not show
I'm using dependency injection for the Interfaces in the constructor method and I have the following code:
What am I doing wrong ?
using Quartz;
using Quartz.Impl;
using Quartz.Logging;
using Microsoft.Extensions.Logging;
using AAA.Service.Actuations;
namespace AAA.Service.StartUppers
{
public class StartUpService : IStartUpService
{
private readonly IScheduler _quartzScheduler;
public StartUpService(IScheduler quartzScheduler)
{
this._quartzScheduler = quartzScheduler;
}
public async void startTasks()
{
try
{
IJobDetail deviceActuation = JobBuilder.Create<ActuationService>()
.WithIdentity("deviceActuator", "group2")
.Build();
ITrigger triggerDeviceActuation = TriggerBuilder.Create()
.WithIdentity("triggerDeviceActuation", "group2")
.WithCronSchedule("0 0/1 * * * ?")
.Build();
await _quartzScheduler.ScheduleJob(deviceActuation, triggerDeviceActuation);
await _quartzScheduler.Start();
}
catch (Exception e)
{
throw new System.Exception("An error ocurred - " + e.Message);
}
}
}
}
And the class where I have the execute method
namespace AAA.Service.Actuations
{
public class ActuationService : IJob
{
private readonly IUserRepository _userRepo;
private readonly IDeviceRepository _deviceRepo;
private readonly IMeasurementAdapter _measurementsAdapter;
public ActuationService( IUserRepository userRepo, IDeviceRepository deviceRepo, IMeasurementAdapter measurementsAdapter)
{
this._userRepo = userRepo;
this._deviceRepo = deviceRepo;
this._measurementsAdapter = measurementsAdapter;
}
public async Task Execute(IJobExecutionContext context)
{
Console.WriteLine("HELLO");
}
}
}
Qaurtz doesn't know how to instantiate IUserRepository, IDeviceRepository, IMeasurementAdapter. To use DI in Quartz, you must use the Quartz.Extensions.DependencyInjection package.
Call UseMicrosoftDependencyInjectionJobFactory in Quartz configuration:
public void ConfigureServices(IServiceCollection services)
{
//Your services...
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
//Your configuration
}
}
Check out the documentation.
Related
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;
}
}
Please go through the below scenario. This is based on C# .Net Web API.
I have to call GetMonthlyData() which is located in BL layer. BL Layer related dependencies are initialized in the Controller and it works fine.
It is required to call the same GetMonthlyData() as a Job and I am using Quartz for that. However when initiating the Job I am getting an issue since the dependencies are not resolved at that time since the call does not goes through the controller. How can I resolve this issue?
FileProcessController.cs -> This is the controller which initiates the Dependency Injection
[RoutePrefix("api/FileProcess")]
public class FileProcessController : ApiController
{
private readonly IFileProcessManager _fileProcessManager;
public FileProcessController(IFileProcessManager fileProcessManager)
{
this._fileProcessManager = fileProcessManager;
}
[HttpGet]
[Route("MontlyData")]
public MonthlyProcessDataDM GetMonthlyData()
{
try
{
var result = this._fileProcessManager.GetMonthlyData();
return result;
}
catch (Exception ex)
{
throw ExceptionStatusCode.ThrowHttpErrorResponseAndMessage(ex);
}
}
}
}
FileProcessManager.cs -> This contains the implementation of GetMonthlyData()
public class FileProcessManager : IFileProcessManager
{
public FileProcessManager(IFileProcessManagerRepo ifileProcessManagerRepo)
{
}
public MonthlyProcessDataDM GetMonthlyData()
{
//Code to be executed
}
}
Interface IFileProcessManager
public interface IFileProcessManager
{
MonthlyProcessDataDM GetMonthlyData();
}
Execution of the above flow works properly. In the next scenario, I need to call GetMonthlyData() using Quartz. For that
Global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
System.Web.Http.GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
JobScheduler.Start(); // This is the scheduler for the above mentioned job.
}
JobScheduler.cs
public class JobScheduler
{
public static void Start()
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
scheduler.Start();
IJobDetail job = JobBuilder.Create<MonthlyProcessManager>().Build(); // This is the Implementation
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
I tried to implement as below to resolve the dependency injection, However it throws null reference exception pointing the dependency injection code.
MonthlyProcessManager.cs
public class MonthlyProcessManager : Quartz.IJob
{
public IFileProcessManager _fileProcessManager;
public MonthlyProcessManager(IFileProcessManager fileProcessManager)
{
_fileProcessManager = fileProcessManager;
}
public void Execute(Quartz.IJobExecutionContext context)
{
MonthlyProcessDataDM monthlyProcessDataVM = _fileProcessManager.GetMonthlyData();
}
}
How can I resolve this issue. Thanks in advance.
I have different classes as descendants of a base class (Worker).
Each of these classes has it's own quartz job and trigger and a
callback.
public class Worker1 : Worker, ICallback
{
IScheduler scheduler;
public Worker1(IScheduler scheduler)
: base("Worker1")
{
this.scheduler = scheduler;
IJobDetail job = JobBuilder.Create<MonitorJob>()
.WithIdentity(name + "Job")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity(name + "Trigger")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(1)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
public void Callback()
{
Console.WriteLine(name + " callback " + DateTime.Now.ToLongTimeString());
}
}
Now I want on trigger of job1 (from worker1) a callback to worker1.callback.
Same with job2 (from worker2) a callback to worker2.callback.
With autofac I am able to inject a callback into my job - however I can only inject a common callback, not as I like one for each class.
public class MonitorJob : IJob
{
ICallback callback;
public MonitorJob(ICallback callback)
{
this.callback = callback;
}
public void Execute(IJobExecutionContext context)
{
callback.Callback();
}
}
my main class creates the autofac container
container = ConfigureContainer(new ContainerBuilder()).Build();
using (container.BeginLifetimeScope())
{
workers.Add(container.Resolve<Worker1>());
workers.Add(container.Resolve<Worker2>());
}
var factory = container.Resolve<ISchedulerFactory>();
var scheduler = factory.GetScheduler();
scheduler.Start();
internal static ContainerBuilder ConfigureContainer(ContainerBuilder cb)
{
cb.RegisterModule(new QuartzAutofacFactoryModule());
cb.RegisterModule(new QuartzAutofacJobsModule(typeof(QuartzScheduler.MonitorJob).Assembly));
cb.RegisterType<Worker1>().AsSelf();
cb.RegisterType<Worker2>().AsSelf();
return cb;
}
Autofac creates the job and also could inject it with a callback, however I want the right callback from the worker class that "owns" the job.
How would this be possible?
Otherwise I would need to somehow propagate from one common callback to the respective worker class.
Thank you in advance
I succeeded by creating a generic job:
public class MonitorJob<T> : IJob where T:IWorker
{
T worker;
public MonitorJob(T worker)
{
this.worker = worker;
}
public void Execute(IJobExecutionContext context)
{
worker.Callback();
}
}
In my custom worker class I create the monitor job for this worker:
public Worker1(IScheduler scheduler)
{
this.scheduler = scheduler;
IJobDetail job = JobBuilder.Create<MonitorJob<Worker1>>()
.WithIdentity(name + "Job")
.Build();
[...]
}
and so the right callback class will be resolved by:
container.RegisterType<Worker1>().AsSelf().SingleInstance();
container.RegisterType<MonitorJob<Worker1>>().AsSelf();
[...]
workers.Add(scope.Resolve<Worker1>());
so always the right callback is executed for each individual job.
Autofac has no way to know how to bind ICallback and IJob. How would like to bind them ?
The simplest solution is to introduce a dependency between these 2 classes
public class MonitorJob : IJob
{
public MonitorJob(Worker1 worker1)
{
this._worker1 = worker1;
}
private readonly Worker1 _worker1;
public void Execute(IJobExecutionContext context)
{
this._worker1.Callback();
}
}
If it is not possible you can use Keyed service :
public class MonitorJob : IJob
{
public MonitorJob([WithKey("Worker1")]ICallback callback)
{
this._callback = callback;
}
private readonly ICallback _callback;
public void Execute(IJobExecutionContext context)
{
this._callback.Callback();
}
}
and you will have to register it this way cb.RegisterType<Worker1>().Keyed<ICallback>("Worker1")
If it is still not possible to modify your code this way, you can create a new module to add parameters :
public class CallbackModule : Module
{
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
if (registration.Services.OfType<TypedService>()
.Any(s => s.ServiceType == typeof(IJob)))
{
registration.Preparing += (sender, e) =>
{
// bind ICallback and IJob here
if (registration.Activator.LimitType == typeof(Job1))
{
e.Parameters = e.Parameters
.Concat(new TypedParameter[] { TypedParameter.From<ICallback>(e.Context.Resolve<Worker1>()) });
}
};
}
}
}
And the registration can be made this way :
ContainerBuilder cb = new ContainerBuilder();
cb.RegisterModule(new CallbackModule());
cb.RegisterType<Worker1>().As<ICallback>().AsSelf();
cb.RegisterType<Worker2>().As<ICallback>().AsSelf();
IContainer container = cb.Build();
By the way, the following code contains a mistake:
using (container.BeginLifetimeScope())
{
workers.Add(container.Resolve<Worker1>());
workers.Add(container.Resolve<Worker2>());
}
You create a new ILifetimeScope but don't use it. The following code should be use instead :
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
workers.Add(scope.Resolve<Worker1>());
workers.Add(scope.Resolve<Worker2>());
}
If you want to be notified when the execute method is called, you can implement a JobListener for this. You can create one listener for all jobs or multiple listeners, one per job type perhaps, and then apply the appropriate matcher. Here's a link that might be useful with more details on job listeners and listeners in general: Quartz.Net Job Listeners, Part 3 of Quartz.net Listeners in detail
I am attempting a very simple application with NServiceBus and Ninject.
I am attempting to use Ninject as the container for NServiceBus, but I am getting the following error - "NServiceBus.IStartableBus is not registered in the container."
I'm sure the answer is quite obvious... just not to me!
My code is as follows
public class StartApp : IWantCustomInitialization, IWantToRunWhenBusStartsAndStops
{
private static IKernel _kernel;
public IBus Bus { get; set; }
public void Init()
{
Configure.Serialization.Json();
}
public void Start()
{
_kernel = new StandardKernel();
Configure.With()
.NinjectBuilder(_kernel)
.CreateBus()
.Start();
Bus.Send(new TestMessage {Id = Guid.NewGuid(), MessageText = "Bloop"});
}
public void Stop()
{
}
}
namespace NServiceBus_Ninject_Simple
{
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server
{ }
}
This google groups discussion is about the same issue.
It seems you are creating the configuration in the wrong place.
It should look like this:
public abstract class DefaultEndpointConfig
: IConfigureThisEndpoint
, IWantCustomInitialization
{
IWantCustomInitialization.Init()
{
Configure
.With()
.NinjectBuilder();
// + any other config;
// Call optional endpoint specific config
Init();
}
public virtual void Init()
{
}
}
See here (Johannes Gustafsson)
It needs to be done in the EndPoint-Configuration (for every endpoint, this is why he suggests using a base class) and it needs to implement IConfigureThisEndpoint.
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!