Base solution for your next web application
Open Closed

Notification is not sent #4206


User avatar
0
Ricavir created

Hi,

I'm testing a new feature that needs to notify every user of current tenant. I've added my notification definition to the notification provider.

I've got following exception when sending it :

ERROR 2017-11-23 08:45:50,525 [10 ] Mvc.ExceptionHandling.AbpExceptionFilter - Can not set TenantId to 0 for IMustHaveTenant entities! Abp.AbpException: Can not set TenantId to 0 for IMustHaveTenant entities!

I try to set tenantId with _unitOfWorkManager.Current.SetTenantId(x) but same exception

I try to launch the notification from different locations : from AppService (Exception raised) and from BackgroundWorker (Exception not raised but notification not sent as well)

I try with existing notification "SendMessageAsync" (existing on your template project for test purpose) and I've got same exception.

I must miss something because when I create a new User, the welcome notification is sent and received by this new user.

Any idea ?


13 Answer(s)
  • User Avatar
    0
    Ricavir created

    Sorry, error on this post title. Title is "Notification not sent"

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @Ricavir,

    Can you share your code for sending notification ? Usage in app service would be better. We will take a look and try to find the problem.

  • User Avatar
    0
    Ricavir created

    Hi @ismcagdas,

    Here is a simple test in AppService :

    [AbpAuthorize(AppPermissions.Pages_Tenant_InputDatas_Create)]
            protected async Task CreateAccountingCode(AccountingCodeEditDto input)
            {
                var accountingCodeVar = input.MapTo<AccountingCode>();
                //Check duplicate code
                if (_accountingCodeRepository.GetAll().Where(c => c.Code == accountingCodeVar.Code).Count() > 0)
                    throw new UserFriendlyException(L("DuplicatedCode_Error_Message"), L("DuplicatedCode_Error_Detail", accountingCodeVar.Code));
                await _accountingCodeRepository.InsertAsync(accountingCodeVar);
    
                await _appNotifier.SendMessageAsync((await UserManager.GetUserByIdAsync((long)AbpSession.UserId)).ToUserIdentifier(), "test");            
            }
    

    As you can see, I just try to send a message with SendMessageAsync notification.

    How can I share my code for you to test ? With a ZIP file of whole project or by taking control of my PC with a teamviewer session for instance ? I would prefer by a teamviewer session.

  • User Avatar
    0
    aaron created
    Support Team
    1. Is the current user a host user?
    2. Can you check if _unitOfWorkManager.SaveChanges() before SendMessageAsync works?
    3. Show the full stack trace.

    Note: (await UserManager.GetUserByIdAsync((long)AbpSession.UserId)).ToUserIdentifier() can be simplified to AbpSession.ToUserIdentifier()

  • User Avatar
    0
    Ricavir created

    Hi @aaron

    1 - Current user is not an host user. 2 - I've added _unitOfWorkManager.Current.SaveChanges() before sending notification... and it works ! But why am I obliged to add it ? 3 - Here is the full stack trace error without _unitOfWorkManager.Current.SaveChanges():

    ERROR 2017-11-24 15:12:47,152 [14   ] Mvc.ExceptionHandling.AbpExceptionFilter - Can not set TenantId to 0 for IMustHaveTenant entities!
    Abp.AbpException: Can not set TenantId to 0 for IMustHaveTenant entities!
       à Abp.EntityFrameworkCore.AbpDbContext.CheckAndSetMustHaveTenantIdProperty(Object entityAsObj)
       à Abp.EntityFrameworkCore.AbpDbContext.ApplyAbpConcepts()
       à Abp.EntityFrameworkCore.AbpDbContext.<SaveChangesAsync>d__36.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.<SaveChangesInDbContextAsync>d__20.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.<SaveChangesAsync>d__12.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à Abp.Notifications.NotificationStore.<InsertNotificationAsync>d__8.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à Abp.Threading.InternalAsyncHelper.<AwaitTaskWithPostActionAndFinally>d__1.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
       à Abp.Notifications.NotificationPublisher.<PublishAsync>d__12.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à Abp.Threading.InternalAsyncHelper.<AwaitTaskWithPostActionAndFinally>d__1.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       à ELEVEN_SOFT.Logisav.Notifications.AppNotifier.<SendMessageAsync>d__5.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       à ELEVEN_SOFT.Logisav.InputData.AccountingCodeAppService.<CreateAccountingCode>d__10.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       à ELEVEN_SOFT.Logisav.InputData.AccountingCodeAppService.<CreateOrUpdateAccountingCode>d__8.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__27.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       à Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__25.MoveNext()
    --- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
       à Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
       à Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
       à Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextExceptionFilterAsync>d__24.MoveNext()
    
  • User Avatar
    0
    aaron created
    Support Team

    it works ! But why am I obliged to add it ?

    NotificationStore.InsertNotificationAsync calls SaveChangesAsync in using(_unitOfWorkManager.Current.SetTenantId(null)). That's because NotificationInfo is MultiTenancySides.Host.

    Since your AccountingCode hasn't been saved, ApplyAbpConcepts will try to set its audit properties (i.e. TenantId = 0). An error is thrown in CheckAndSetMustHaveTenantIdProperty because AccountingCode is IMustHaveTenant.

    Therefore, you should call SaveChanges before SendMessageAsync.

  • User Avatar
    0
    Ricavir created

    Ok, this information is helping.

    So, going back to my goal (send notification from backgroundworker), notifications are still not sent. I don't have any exception in the stack trace.

    Here is the code of my notification :

    public async Task SchedulerNewEventCreatedAsync(int? tenantId, int eventCount)
            {
                var notificationData = new LocalizableMessageNotificationData(
                    new LocalizableString(
                        "SchedulerNewEventCreatedNotificationMessage",
                        LogisavConsts.LocalizationSourceName
                        )
                    );
    
                notificationData["eventCount"] = eventCount;            
    
                await _notificationPublisher.PublishAsync(AppNotificationNames.SchedulerNewEventCreated, notificationData, severity: NotificationSeverity.Success, tenantIds: new[] { tenantId });
            }
    

    This code should send a notification to all users of selected tenant (at least users who have subscribed to this type of notification)

    Here is the code on backgroundworker :

    [UnitOfWork]
            protected override void DoWork()
            {
                var tenantsForEventSchedule = _tenantRepository.GetAllList(tenant => tenant.IsActive);
    
                foreach (var tenant in tenantsForEventSchedule)
                {
                    try
                    {
                        
                        if (AsyncHelper.RunSync(() => SettingManager.GetSettingValueForTenantAsync<bool>(AppSettings.TenantEvent.Scheduler_Activated, tenant.Id)) == true)
                        {
                            //define period to search contracts
                            var today = Clock.Now.Date;
                            int dayCountBeforeEventCreation = AsyncHelper.RunSync(() => SettingManager.GetSettingValueForTenantAsync<int>(AppSettings.TenantEvent.Scheduler_DayCount_BeforeEventCreation, tenant.Id));
                            var searchDate = today.AddDays(dayCountBeforeEventCreation);
                            bool includeSuspendedContracts = AsyncHelper.RunSync(() => SettingManager.GetSettingValueForTenantAsync<bool>(AppSettings.TenantEvent.Scheduler_SuspendedContractIncluded, tenant.Id));                        
    
    
                            var contractsFound = _contractScheduleRepository
                                .GetAll()
                                .Include(eq => eq.Equipments)
                                .Include(c => c.Contract)
                                .Where(cs => cs.Contract.IsActive == true)
                                .WhereIf(!includeSuspendedContracts, cs => cs.Contract.IsSuspended == false)
                                .Where(cs => cs.ScheduleDate <= searchDate).ToList();
                  
                            int eventsCreatedCount = 0;
    
                            foreach (var contractSchedule in contractsFound)
                            {                            
                                using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
                                {                               
                                        Event _event = new Event();
                                        _event.AddressId = contractSchedule.Contract.AddressId;
                                        _event.ContractId = contractSchedule.Contract.Id;
                                        _event.ContractScheduleId = contractSchedule.Id;
                                        _event.TenantId = tenant.Id;                                    
                                        //
                                                    SOME ENTITY PROPERTY FILLING
                                       //
                                        //SuccessiveId
                                        AsyncHelper.RunSync(() => _eventManager.GenerateSuccessiveId(_event));
    
                                        AsyncHelper.RunSync(() => _eventRepository.InsertAsync(_event));
                                        eventsCreatedCount++;                                    
                                    }
                                    //Save change after each loop
                                    _unitOfWorkManager.Current.SaveChanges();
                                }
    
                                if (eventsCreatedCount > 0)
                                {
                                    AsyncHelper.RunSync(() => _appNotifier.SchedulerNewEventCreatedAsync(tenant.Id, eventsCreatedCount));
                                }
                            }
                        }                    
                    }
                    catch (Exception exception)
                    {
                        Logger.Error($"Event scheduler of tenant {tenant.TenancyName} has rised an exception !");
                        Logger.Error(exception.Message, exception);
                    }
                }
    

    No notification is sent from that piece of code. Therefore, I've tried from a basic AppService like :

    [AbpAuthorize(AppPermissions.Pages_Tenant_InputDatas_Create)]
            protected async Task CreateAccountingCode(AccountingCodeEditDto input)
            {
                var accountingCodeVar = input.MapTo<AccountingCode>();
                //Check duplicate code
                if (_accountingCodeRepository.GetAll().Where(c => c.Code == accountingCodeVar.Code).Count() > 0)
                    throw new UserFriendlyException(L("DuplicatedCode_Error_Message"), L("DuplicatedCode_Error_Detail", accountingCodeVar.Code));
                await _accountingCodeRepository.InsertAsync(accountingCodeVar);
                _unitOfWorkManager.Current.SaveChanges();
                await _appNotifier.SchedulerNewEventCreatedAsync(AbpSession.TenantId, 30);
            }
    

    But, again, nothing received.... and no exception in stack trace. Sorry for all that code, but I'm really blocked right now :(

  • User Avatar
    0
    aaron created
    Support Team
    1. Are you using a single database?
    2. Can you check if PublishAsync to a specific user works?
    3. Can you inject INotificationSubscriptionManager and check if GetSubscriptionsAsync returns the users?
  • User Avatar
    0
    Ricavir created
    1. Yes I'm in a single database
    2. PublishAsync works for a specific user BUT only when fired from an AppService linked to the current tenant. It doesn't work when notification is fired from a background worker (then from host side) > in this case, getting a user from userrepository doesn't work.
    3. I injected INotificationSubscriptionManager and tenant users have subscription (like it is the case also for existing NewUser notification)

    I've tested many things from host side (and from background worker) and tenant side (and from AppService). I can report the following behavior :

    • From host side (background worker)
      • Sending notification to specific tenant doesn't work. Even if setting current unit of work to target tenant
      • Sending notification to specific user doesn't work neither
    • From tenant side (AppService)
      • Sending notification to tenant (and therfore to any subscribed user) doesn't work. I even try to set tenantId to null when publishing notification...
      • Sending notification directly to a specific user works !

    Is it an issue due to data filters or authorization or other thing that I've missed ? Have you try to send a notification to a tenant and to all subscribed user ?

  • User Avatar
    0
    aaron created
    Support Team

    Unlikely to be authorization if it is not logged.

    Sending to subscribed users is done by background job too, so it may be the same issue. Can you check if the TenantNotification and UserNotifications are created in database?

  • User Avatar
    0
    Ricavir created

    No, nothing in database. Only notifications that I've sent directly to specific user and that I've received on UI are on database.

  • User Avatar
    0
    Ricavir created

    I've tested a lot of cases. Are you sure that it is possible to send notifications to all users of a specific tenant from host side ? Because in this case, AbpSession.TenantId is always null... and that's probably why it's not working.

    I need to find a solution to overcome this issue ; this feature is a must have for my customer.

  • User Avatar
    0
    Ricavir created

    Github issue created : [https://github.com/aspnetzero/aspnet-zero-core/issues/614])