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;
}
}
Related
I am completely re-writing this question as nothing has made sense to anybody - I apologize for the issues.
To start, I have a Singleton service, called LocalSystemService, that handles RESTful communications between the Blazor Server App and a separate Web API system running on a separate server. I have added that service into my Blazor application using the following call:
services.AddScoped<ILocalSystemService, LocalSystemService>();
I have now moved away from a simple Timer and to a separate Service in response to other articles I have read. This service is called CheckLDC and is registered using the following:
services.AddSingleton<CheckLDC>();
That service is constructed as follows:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using Telerik.Blazor;
using Telerik.Blazor.Components;
using Frontend.Data;
using Frontend.Services;
namespace Frontend.Services
{
public class LDCExecutedEventArgs : EventArgs { }
public class CheckLDC : IDisposable
{
public event EventHandler<LDCExecutedEventArgs> JobExecuted;
void OnJobExecuted()
{
JobExecuted?.Invoke(this, new LDCExecutedEventArgs());
}
#region Globals
static ReaderWriterLock locker = new ReaderWriterLock();
private System.Timers.Timer checkRemoteData;
private bool _Running;
[Inject]
public ILocalSystemService LocalSystemService { get; set; }
[Inject]
public ILogger<CheckLDC> _logger { get; set; }
[Inject]
public IMemoryCache _cache { get; set; }
private const string LocationCacheName = "LocalSystem";
#endregion
public void StartExecuting()
{
if (!_Running)
{
// Initiate a Timer
checkRemoteData = new System.Timers.Timer(1000);
checkRemoteData.Elapsed += HandleTimer;
checkRemoteData.AutoReset = true;
checkRemoteData.Enabled = true;
_Running = true;
}
}
private async void HandleTimer(object source, ElapsedEventArgs e)
{
**This call results in a NULL for the LocalSystemService!!!**
**if (LocalSystemService.IsThereAnUpdate().Result)**
{
await Task.Run(() =>
{
try
{
locker.AcquireWriterLock(int.MaxValue);
if (!_cache.TryGetValue(LocationCacheName, out transferSystem))
{
//We need to grab everything:
JsonSerializer serializer = new JsonSerializer();
#region Location
try
{
_cache.Set(LocationCacheName, LocalSystemService.GetLocalSystem().Result);
}
catch (Exception locWriteX)
{
_logger.LogError("Failed to restore location locally with the error: " + locWriteX.ToString());
}
#endregion
}
}
finally
{
locker.ReleaseWriterLock();
}
});
}
// Notify any subscribers to the event
OnJobExecuted();
}
public void Dispose()
{
if (_Running)
{
checkRemoteData?.Dispose();
}
}
}
}
I then placed this code in my main component behind
[Inject]
public ILocalSystemService LocalSystemService { get; set; }
[Inject]
public CheckLDC CheckTheBackend {get; set;}
Note that I am using a Memory Cache to store data across requests. Wit the injection in place, my OnInit method looks as follows:
protected override async Task OnInitializedAsync()
{
await Task.Run(() =>
{
//This call uses the LocalSystemService to grab and store the main data class into the cache
CurrentSystem = CreateCaches().Result;
});
//These are my event subscriptions
CheckTheBackend.JobExecuted += HandleJobExecuted;
CheckTheBackend.StartExecuting();
}
Finally, the method being called on JobExecute is:
public async void HandleJobExecuted(object sender, LDCExecutedEventArgs e)
{
await InvokeAsync(StateHasChanged);
}
Now I am getting a NULL exception when trying to call the LocalSystemService from the CheckLDC's HandleTimer event call - I bold typed the call that continues to fail. I have tried AddTransient and AddScope for the LocalSystemService but nothing works. In the Blazor app, I can call LocalSystemService without any issues - but is ALWAYS fails in the CheckLDC singleton.
Any ideas?
You can't use property injection here - that is for components only.
You will need to use constructor injection for CheckLDC
private readonly ILocalSystemService LocalSystemService;
private readonly ILogger<CheckLDC> _logger;
private readonly IMemoryCache _cache;
public CheckLDC(ILocalSystemService localSystemService,
ILogger<CheckLDC> logger,
IMemoryCache cache)
{
LocalSystemService = localSystemService;
_logger = logger;
_cache = cache;
}
Sorry, but it is hard to follow the flow of execution without having a complete reproducible code sample. However, the following code snippet, based on the FetchData page, and your code (I tried to be as true as possible to your code) works. The commented code is superfluous. Copy and test...then try to apply it to your program, and report of issues.
#page "/fetchdata"
#using WebApplication1.Data
#using System.Timers;
#inject WeatherForecastService ForecastService
#implements IDisposable
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
#if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
#foreach (var forecast in forecasts)
{
<tr>
<td>#forecast.Date.ToShortDateString()</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
#code {
private WeatherForecast[] forecasts;
private static System.Timers.Timer checkRemoteData;
protected override async Task OnInitializedAsync()
{
// forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
//await Task.Run(() =>
//{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now); //.Result;
// What is ShowModal? Are you setting it to true somewhere ?
// Perhaps this is why you do not see the changes, if you design
// to show the changes in a window modal
// ShowModal = false;
//});
//This works and calls the main method every second
checkRemoteData = new System.Timers.Timer(4000);
checkRemoteData.Elapsed += OnTimedEvent;
checkRemoteData.AutoReset = true;
checkRemoteData.Enabled = true;
}
private async void OnTimedEvent(Object source, ElapsedEventArgs e)
{
//if (LocalSystemService.IsThereAnUpdate().Result)
//{
// await InvokeAsync(StateHasChanged);
// I have used the same call as in the OnInit - niether one works!
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
await InvokeAsync(() => StateHasChanged()); //.ConfigureAwait(false);
// What is this for ? Are you trying to emulate
// delay? Just increase the interval (to 4000...)
// await Task.Delay(500);
// Not needed
// await InvokeAsync(StateHasChanged);
//}
}
public void Dispose()
{
checkRemoteData.Elapsed -= OnTimedEvent;
}
}
Update:
I've added the following method to the WeatherForecastService in order to emulate LocalSystemService.IsThereAnUpdate().Result
public Task<bool> IsThereAnUpdate()
{
return Task.FromResult( true);
}
And added also this:
if (ForecastService.IsThereAnUpdate().Result)
{
}
And now when the IsThereAnUpdate returns true, the UI is updated, and when it returns false, it does not.
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.
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]
}
}
}
}
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.
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!