Hi,
Im trying to create a RecurringJob with Hangfire but i dont know what should be the correct approach to achieve this.
I'm trying to define my Recurring Jobs in the Postinitialize Method of my Web Module:
var clientDomainService = IocManager.Resolve<ClientDomainService>();
RecurringJob.AddOrUpdate(() => clientDomainService.NotifyAppointment(), Cron.Minutely);
ClientDomainService inherits form DomainServiceBase, but when the recurring job executes, I got a NullReferenceException when executing this line (CurrentUnitOfWork is null):
using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
After that, i decided to manually inject a IUnitOfWork in my domain service by its constructor, and it works; but now i noticed that i'm not able to navigate throw my entities foreign key properties: I received the following error:
Additional information: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
So, to continue with my tests i decided to include my navigation properties in the result of my repository query, but the problem now is when I query Users in the userManager i received the following exception:
Additional information: The operation cannot be completed because the DbContext has been disposed.
Could you guys please help me and tell me what is the correct approach to use my domain services methods in a recurring job?
Thanks in advance.
17 Answer(s)
-
0
Hi,
Can you share sample code for this case ?
Thanks.
-
0
Thanks @ismcagdas
This is my code:
in WebModule.cs PostInitialize method:
public override void PostInitialize() { ... var clientDomainService = IocManager.Resolve<IClientDomainService>(); RecurringJob.AddOrUpdate(() => clientDomainService.NotifyAppointment(), Cron.Minutely); }
IClientDomainService:
public interface IClientDomainService : IDomainService { void NotifyAppointment(); }
ClientDomainService:
public class ClientDomainService : BowDomainServiceBase, IClientDomainService { private readonly IContractDomainService _contractDomainService; private readonly IAppNotifier _appNotifier; private readonly UserManager _userManager; public ClientDomainService(IContractDomainService contractDomainService, IAppNotifier appNotifier, UserManager userManager) { _contractDomainService = contractDomainService; _appNotifier = appNotifier; _userManager = userManager; } public void NotifyAppointment() { using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant)) { List<Appointment> newAppointments = _contractDomainService.GetAppointmentsNextHours(2); foreach (Appointment appointment in newAppointments) { long? userId = appointment.Employee.UserId; if (usuarioId.HasValue) { User user= _userManager.Users.Where(u => u.Id == userId).FirstOrDefault(); _appNotifier.SendAppointmentNotification(appointment.Date, appointment.Client.Fullname, userId); } } } } }
As I said in my first post, I'm getting null reference exception when i try to use CurrentUnitOfWork, I'm not able to navigate throw the navigation properties in my entity (Appointment) and when I try to query Users in UserManager it says that the dbContext has been disposed.
Thank you so much.
-
0
Hi,
Can you try to change your method like this,
[UnitOfWork] public virtual void NotifyAppointment() { ..... }
-
0
Yes it works!
Could you please explain me what this change did in the execution of my code?
Thank you so much!
-
0
Hi,
I think in your version of ABP, this method is not intercepted by ABP, because of that it is not UnitOfWork by default. Adding virtual and UnitOfWork attributes allow dependency injection to intercept this method and wraps it inside an unit of work.
-
0
Hi, thank you for your help.
I am working with ABP version 1.5.0 and aspnet Zero versiĆ³n 3.2 (commits until March 7, 2017).
As you can see I'm working with a very new release of aspnetboilerplate, so my method should be intercepted as intended. Maybe, is there something else i am missing?
Thanks in advance.
-
0
Hi,
Ok, domain services are not UoW by default, so if you call a domain service in a method which is not UoW (background job in your case), this is the expected behaviour.
You don't have to add this attribute if you are calling domain service from app service or controller.
So, no problem in this case :)
-
0
Sorry again, but this recurring job is acting very weird.
I'm testing it againg and i did not do any changes to my code and my recurring job is not executing. In hangfire i am receiving the following error: (Trying to translate it correctly)
"Unable to load file or assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or any of its dependencies. System unable to find the specified file".
Sorry for all the inconvenience but i dont know what can i do.
Thanks in advance.
Edit:
After several try and error test i discover the following:
If I remove the [UnitOfWork] and the virtual keyword of my method it starts to execute my method again, but with all the errors y talked at my first post. After that, i add the [UnitOfWork] and the virtual keyword again and it starts to work.
I dont understand why it is working like that.
-
0
Hi,
I think when you add virtual and UnitOfWork attributes, hangfire cannot instantiate your saved background jobs when starting your web app.
Can you try this:
In PreInitialize method of your web module add below configuration for hangfire
GlobalConfiguration.Configuration.UseActivator(new WindsorHangFireJobActivator());
And add below class to your web project,
public class WindsorHangFireJobActivator : JobActivator { public override object ActivateJob(Type type) { return IocManager.Instance.Resolve(type); } }
You can delete all background jobs in hangfire tables manually if you face any problems.
-
0
Thank you so much! It is working great right now.
Hope this will help everyone trying to do something similar.
-
0
Hi. I am sorry to say that there are still problems with this.
After it worked on my computer, I decide to push my code and execute it in other PC, after that I keep getting the following error in Hangfire:
Could not load file or assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
That was the same error I was getting before in my PC. If this error happens, my method is not even triggered.
To continue my tests I pull my code in other PC and got the same error; I even try it on Azure with the same results.
The only way I am able to make this to work is with the following steps, but I dont understand why:
Remove [UnitOfWork] from my method, compile and execute again. By doing this, my method is triggered again as it is supposed, but as we have seen before, it is not able to load all context and entities.
Stop execution and add again [UnitOfWork] annotation, compile and execute. At this time it triggered my method and works as expected.
By doing that, hangfire is able to execute my method as expected, but it is not a viable solution (it is also not possible in Azure).
Any ideas?
-
0
Hi,
Can you try to remove all background job data from hangfire database and try this scenario again ? I think when you add UnitOfWork attribute, the old jobs causes this problem.
Every time when you run your app, the below code generates a new background job because you didn't define a unique name for your job.
RecurringJob.AddOrUpdate(() => clientDomainService.NotifyAppointment(), Cron.Minutely);
I also suggest you to define a unique name for your job so you will have a single job for NotifyAppointment.
-
0
Hi, and thank you so much for all your help.
Based on your recommendation I added a name to my Job, now when I look at the Hangfire dashboard, there is only one recurring job.
RecurringJob.AddOrUpdate("NotifyAppontmentJob", () => clientDomainService.NotifyAppointment(), Cron.Minutely);
After that I continue to drop all hangfire tables, so there is nothing stored related with my jobs (I have done this several times in my test).
Then, I Execute my solution and i got the same error :
Could not load file or assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
In your tests, did you never got that error? I will put my preinitlize code, so if you can check if everything is ok :
public override void PreInitialize() { //Use database for language management Configuration.Modules.Zero().LanguageManagement.EnableDbLocalization(); //Configure navigation/menu Configuration.Navigation.Providers.Add<AppNavigationProvider>();//SPA! Configuration.Navigation.Providers.Add<FrontEndNavigationProvider>(); Configuration.Navigation.Providers.Add<MpaNavigationProvider>();//MPA! Configuration.Modules.AbpWebCommon().MultiTenancy.DomainFormat = WebUrlService.WebSiteRootAddress; //Uncomment these lines to use HangFire as background job manager. Configuration.BackgroundJobs.UseHangfire(configuration => { configuration.GlobalConfiguration.UseSqlServerStorage("Default"); }); GlobalConfiguration.Configuration.UseActivator(new WindsorHangFireJobActivator()); //Uncomment this line to use Redis cache instead of in-memory cache. //Configuration.Caching.UseRedis(); }
I even try something like this (I dont know if it is correct) :
Configuration.BackgroundJobs.UseHangfire(configuration => { configuration.GlobalConfiguration.UseSqlServerStorage("Default"); configuration.GlobalConfiguration.UseActivator(new WindsorHangFireJobActivator()); });
But still the same problem continues, [UnitOfWork] annotation gives me the same error and my method is not triggered.
Could you try something similar in your code, to check if everything works as expected?
Thanks in advance.
-
0
Hi,
If there is no private data in your database, can you send your project and backup of your database to <a href="mailto:[email protected]">[email protected]</a>. It might be faster to take a look at your project by running it.
Thanks.
-
0
Ok thank you so much!
I'm going to contact you by email with all required data.
Thanks again.
-
0
Hi!
For everyone with the same issue i will post the solution: (thanks @ismcagdas for your support):
The solution to my problem was to create the UnitOfWork inside my background job method:
public class ClientDomainService : BowDomainServiceBase, IClientDomainService { private readonly IContractDomainService _contractDomainService; private readonly IAppNotifier _appNotifier; private readonly UserManager _userManager; private readonly IUnitOfWorkManager _unitOfWorkManager; public ClientDomainService(IContractDomainService contractDomainService, IAppNotifier appNotifier, UserManager userManager, IUnitOfWorkManager unitOfWorkManager) { _contractDomainService = contractDomainService; _appNotifier = appNotifier; _userManager = userManager; _unitOfWorkManager = unitOfWorkManager; } public void NotifyAppointment() { using (_unitOfWorkManager.Begin()) { using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant)) { List<Appointment> newAppointments = _contractDomainService.GetAppointmentsNextHours(2); foreach (Appointment appointment in newAppointments) { long? userId = appointment.Employee.UserId; if (usuarioId.HasValue) { User user= _userManager.Users.Where(u => u.Id == userId).FirstOrDefault(); _appNotifier.SendAppointmentNotification(appointment.Date, appointment.Client.Fullname, userId); } } } } } }
Thanks.
-
0
Thanks @Hozkar,
I forgot to write it here :)