Open Closed

Hangfire #7204


User avatar
0
oleksandra.nakonechniuk created

We are using MultiTenancy. When I created scheduled job and it was executed it doesn't work as expected. It doesn't have the correct context - TenantId is null. But when I was calling job Tenat was not null. Does it mean that hangfire jobs doesn't work with multiTenancy. Or if you can suggest some approach please write.

  • ABP Framework version: vX.X.X
  • UI Type: Angular
  • Database System: EF Core ( PostgreSQL, etc..)
    • Auth Server Separated (for Angular)**: yes
  • Exception message and full stack trace:
  • Steps to reproduce the issue:

4 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    When I created scheduled job and it was executed it doesn't work as expected. It doesn't have the correct context - TenantId is null.

    Please share your code.

    Thanks

  • User Avatar
    0
    oleksandra.nakonechniuk created
    public class AssessmentJobsService : DomainService, IAssessmentJobsService
    {
        #region Fields
    
        private readonly IAssessmentDetailRepository _assessmentDetailRepository;
        private readonly IMediaRepository _mediaRepository;
        private readonly IBlobManager _blobManager;
        private readonly IAssessmentRepository _assessmentRepository;
    
        #endregion
    
        #region Constructors
    
        /// <summary>
        /// Initializes a new instance of the <see cref="AssessmentJobsService" /> class.
        /// </summary>
        /// <param name="assessmentDetailRepository">The assessment repository.</param>
        /// <param name="mediaRepository">The media repository.</param>
        /// <param name="blobManager">The blob manager.</param>
        public AssessmentJobsService(
            IAssessmentDetailRepository assessmentDetailRepository,
            IMediaRepository mediaRepository,
            IBlobManager blobManager,
            IAssessmentRepository assessmentRepository)
        {
            _assessmentDetailRepository = assessmentDetailRepository;
            _mediaRepository = mediaRepository;
            _blobManager = blobManager;
            _assessmentRepository = assessmentRepository;
        }
    
        #endregion
    
        /// <inheritdoc/>
        public void SheduleAssessmentCleanup(long assessmentId)
        {
            BackgroundJob.Schedule(() => CleanUpAssessment(assessmentId), TimeSpan.FromSeconds(30));
        }
    
        public void SheduleEmptyAssessmentCleanup(long assessmentId)
        {
            BackgroundJob.Schedule(() => CleanUpEmptyAssessment(assessmentId), TimeSpan.FromHours(4));
        }
    
        /// <inheritdoc/>
        public void SheduleMediaCleanup(long mediaId)
        {
            BackgroundJob.Schedule(() => CleanUpMedia(mediaId), TimeSpan.FromHours(4));
        }
    
        /// <inheritdoc/>
        public async Task StartTrackingJob(long assessmentDetailId, bool isOneStepProcess)
        {
            // Create a Hangfire job to finish the job if not all methods are called within 5 minutes
            var jobId = BackgroundJob.Schedule(() => CheckProcessVideoTracking(assessmentDetailId, isOneStepProcess), TimeSpan.FromMinutes(3));
            var assessementDetails = await _assessmentDetailRepository.GetAsync(assessmentDetailId);
            assessementDetails.JobId = jobId;
    
            // Sets default status
            assessementDetails.ProcessVideoTrackStatus = (int)ProcessVideoTrackingStatus.InProgress;
            assessementDetails.ProcessVideoTrackStatusValue = ProcessVideoTrackingStatus.InProgress.ToString();
            await _assessmentDetailRepository.UpdateAsync(assessementDetails, true);
        }
    
        /// <inheritdoc/>
        //[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 300, 600, 900, 1800, 3600 })]
        [AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 60, 60, 60 })]
        public async Task CheckProcessVideoTracking(long assessmentDetailId, bool isOneStepProcess)
        {
           var assessmentDetails = await _assessmentDetailRepository.GetAsync(assessmentDetailId)
                ?? throw new Exception($"Job failed. Assessment details were not found");
            var status = (ProcessVideoTrackingStatus)assessmentDetails.ProcessVideoTrackStatus;
    
            if ((isOneStepProcess && status == ProcessVideoTrackingStatus.ProcessData) ||
            (!isOneStepProcess && status == (ProcessVideoTrackingStatus.ProcessData | ProcessVideoTrackingStatus.Report | ProcessVideoTrackingStatus.Overlay)))
            {
                return;
            }
    
            throw new Exception($"Job failed. Status {status}");
        }
    
        /// <inheritdoc/>
        public async Task CleanUpMedia(long mediaId)
        {
            var mediaIsInUse = await _mediaRepository.CheckMediaWithAssessmentDetails(mediaId);
            if (!mediaIsInUse)
            {
                var media = await _mediaRepository.FindAsync(mediaId);
                if(media == null)
                {
                    return;
                }
                await _mediaRepository.DeleteAsync(mediaId);
                await _blobManager.DeleteBlob(media?.MediaUrl, media?.FileName);
            }
        }
    
        /// <inheritdoc/>
        public async Task CleanUpMediaMedBridge()
        {
            try
            {
                // Get details from assessment where externalAssessmentId != null
                var mediaIds = await _assessmentDetailRepository.GetListMediaIdAndCleanDetailsMedbridgeAsync();
                if (mediaIds != null)
                {
                    var mediaQueryable = await _mediaRepository.GetQueryableAsync();
                    var medias = mediaQueryable.Where(m => mediaIds.Contains(m.Id)).ToList();
                    if (medias == null)
                    {
                        return;
                    }
    
                    await _mediaRepository.DeleteManyAsync(mediaIds, true);
                    foreach (var media in medias)
                    {
                        await _blobManager.DeleteBlob(media?.MediaUrl, media?.FileName);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
    
    
        /// <inheritdoc/>
        public async Task CleanUpAssessment(long assessmentId)
        {
            var details = await _assessmentDetailRepository.GetListAsync(x => x.AssessmentId == assessmentId);
            var mediaIds = details.Select(x => x.MediaId).ToList();
            await _assessmentDetailRepository.DeleteManyAsync(details);
    
            var medias = await _mediaRepository.GetListAsync(x => mediaIds.Contains(x.Id));
            await _mediaRepository.DeleteManyAsync(medias);
    
            foreach (var media in medias)
            {
                await _blobManager.DeleteBlob(media.MediaUrl, media.FileName);
            }
        }
    
        public async Task CleanUpEmptyAssessment(long assessmentId)
        {
            if (!await _assessmentDetailRepository.CheckAssessmentWithAssessmentDetails(assessmentId))
            {
                var assessment = await _assessmentRepository.FindAsync(assessmentId);
                if (assessment != null)
                {
                    await _assessmentRepository.DeleteAsync(assessment);
                }
            }
        }
    }
    
    

    and in HttpHostModule

     private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
     {
         context.Services.AddHangfire(config =>
         {
             GlobalConfiguration.Configuration.UsePostgreSqlStorage(configuration.GetConnectionString("Default"));
         });
     }
    
  • User Avatar
    0
    oleksandra.nakonechniuk created

    And one moment I use API and we have API-Key authentication.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can add a tenantid parameter to the methods.

    BackgroundJob.Schedule(() => CleanUpMediaMedBridge(tenantid), TimeSpan.FromHours(4));

    public async Task CleanUpMediaMedBridge(Guid? tenantid)
    {
       //https://docs.abp.io/en/abp/latest/Multi-Tenancy#change-the-current-tenant
    }
    

    The CleanUpMediaMedBridge method is called by hangfire. So it will lose all context.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
Do you need assistance from an ABP expert?
Schedule a Meeting
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.2.0-preview. Updated on March 20, 2025, 18:00