Base solution for your next web application
Open Closed

RecurringJob with Hangfire #2645


User avatar
0
hozkar created

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)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Can you share sample code for this case ?

    Thanks.

  • User Avatar
    0
    hozkar created

    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.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Can you try to change your method like this,

    [UnitOfWork]
     public virtual void NotifyAppointment()
    {
    .....
    }
    
  • User Avatar
    0
    hozkar created

    Yes it works!

    Could you please explain me what this change did in the execution of my code?

    Thank you so much!

  • User Avatar
    0
    ismcagdas created
    Support Team

    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.

  • User Avatar
    0
    hozkar created

    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.

  • User Avatar
    0
    ismcagdas created
    Support Team

    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 :)

  • User Avatar
    0
    hozkar created

    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.

  • User Avatar
    0
    ismcagdas created
    Support Team

    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.

  • User Avatar
    0
    hozkar created

    Thank you so much! It is working great right now.

    Hope this will help everyone trying to do something similar.

  • User Avatar
    0
    hozkar created

    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:

    1. 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.

    2. 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?

  • User Avatar
    0
    ismcagdas created
    Support Team

    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.

  • User Avatar
    0
    hozkar created

    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.

  • User Avatar
    0
    ismcagdas created
    Support Team

    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.

  • User Avatar
    0
    hozkar created

    Ok thank you so much!

    I'm going to contact you by email with all required data.

    Thanks again.

  • User Avatar
    0
    hozkar created

    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.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Thanks @Hozkar,

    I forgot to write it here :)