Base solution for your next web application
Open Closed

Notifications From A Tenant To A Host #8319


User avatar
0
rickfrankel created

Hi,

I'm trying to create and send a notification from a tenant to the host account.

I'm using the following code to publish the notification from within the context of my tenant account. await _notificationPublisher.PublishAsync(AppNotificationNames.MyNotification, notificationData);

I've noticed that it get's inserted into AbpNotifications correctly and then the Hangfire job runs to distribute it.

I've traced through the Abp code and found https://github.com/aspnetboilerplate/aspnetboilerplate/blob/c0604b9b1347a3b9581bf97b4cae22db5b6bab1b/src/Abp/Notifications/DefaultNotificationDistributer.cs

My issue is this section I believe (line 187 in GetUsers method)

            if (tenantIds.IsNullOrEmpty() ||
                (tenantIds.Length == 1 && tenantIds[0] == NotificationInfo.AllTenantIds.To<int>()))
            {
                //Get all subscribed users of all tenants
                subscriptions = _notificationStore.GetSubscriptions(
                    notificationInfo.NotificationName,
                    notificationInfo.EntityTypeName,
                    notificationInfo.EntityId
                    );
            }
            
            

When I trace the DB call here I can see that the GetSubscriptions call is made in the context of the tenant that originally created the notification. Thus even though my host users are subscribed to that NotificationName it will never receive it. This is also strange because the GetSubscriptions code has this.

 [UnitOfWork]
    public virtual List<NotificationSubscriptionInfo> GetSubscriptions(string notificationName, string entityTypeName, string entityId)
    {
        using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
        {
            return _notificationSubscriptionRepository.GetAllList(s =>
                s.NotificationName == notificationName &&
                s.EntityTypeName == entityTypeName &&
                s.EntityId == entityId
                );
        }
    }

So it should disable the filter. However this is my DB log (I've sanitized and cleaned it up a little).

@__ef_filter__p_0='False', @__ef_filter__CurrentTenantId_1='2' (Nullable = true), @__notificationName_0='App.MyNotifications' (Size = 96)], CommandType='Text', CommandTimeout='60'] SELECT a.Id, a.CreationTime, a.CreatorUserId, a.EntityId, a.EntityTypeAssemblyQualifiedName, a.EntityTypeName, a.NotificationName, a.TenantId, a.UserId FROM AbpNotificationSubscriptions AS a WHERE (@__ef_filter__p_0 OR (a.TenantId = @__ef_filter__CurrentTenantId_1)) AND (((a.NotificationName = @__notificationName_0) AND a.EntityTypeName IS NULL) AND a.EntityId IS NULL)

You can see that the first paramater is false which means it is checking for the TenantId.

The distributer essentially cannot find any users subscribed to that notification and thus just delete's my abpnotifications row.

Is there any way around this. Is this expected behaviour.

I'm not sure why I'm seeing this behaviour.

Cheers Rick


5 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Do you switch to Host context when publishing notificaiton https://aspnetboilerplate.com/Pages/Documents/Multi-Tenancy#switching-between-host-and-tenants ?

  • User Avatar
    0
    rickfrankel created

    Ok thanks for that hint. I wasn't but I will.

    I have found the cause of the issue. Complex and I'm not entirely sure it is intended behaviour but I understand it now.

    1. The notificationpublisher code has these lines in it. So even if you pass null or empty for the tenant Id's in your notification the publisher will add in the current logged in session one (which in my case is an actual tenant and not the host account).

         if (tenantIds.IsNullOrEmpty() && userIds.IsNullOrEmpty())
         {
             tenantIds = new[] { AbpSession.TenantId };
         }
      
    2. Then there is this code in the DefaultNotificationDistributor, before the call to the _notificationStore.GetSubscriptionsAsync. If this check passes then it will call the GetSubscriptionsAsync that has the disabling of the filters. If this check DOESN'T pass then it will use the tenant ID passed into it and not disable the filter. if (tenantIds.IsNullOrEmpty() || (tenantIds.Length == 1 && tenantIds[0] == NotificationInfo.AllTenantIds.To<int>())) {

    Solutions:

    1. If you want to send a notification from a tenant to the HOST only then switch to the host context before publishing the notification (using (_unitOfWorkManager.Current.SetTenantId(null))

    2. If you are publishing a notification from a tenant and want to send it to any tentant or host subscribed to that particular notification name then set the tenantID to NotificationInfo.AllTenantIds (which is 0).

    Got love digging through the ABP Code :)

    Cheers to anyone else who comes across this.

    Rick

  • User Avatar
    0
    aaron created
    Support Team

    Hi Rick, have you tried passing null in tenantIds array?

    await _notificationPublisher.PublishAsync(
        AppNotificationNames.MyNotification,
        notificationData,
        tenantIds: new[] { null });
    
  • User Avatar
    0
    rickfrankel created

    Looking at the code that should also work as eventually it will get to this part of the code.

    The tenantId would be null in this case and thus it will set the current tenantid in the filter to null and in affect be the host account. Same as just doing the settenantid call yourself really before publishing.

        [UnitOfWork]
        protected virtual async Task&lt;List&lt;NotificationSubscriptionInfo&gt;> GetSubscriptionsAsync(int? tenantId, string notificationName, string entityTypeName, string entityId)
        {
            using (_unitOfWorkManager.Current.SetTenantId(tenantId))
            {
                return await _notificationSubscriptionRepository.GetAllListAsync(s =>
                    s.NotificationName == notificationName &&
                    s.EntityTypeName == entityTypeName &&
                    s.EntityId == entityId
                );
            }
        }
    
  • User Avatar
    0
    aaron created
    Support Team

    Ok. Just wanted to let you know that it's properly supported.

    Good on you for digging through the ABP code too.