首页 > 解决方案 > 租户会话在 IRealTimeNotifier 中丢失

问题描述

我们正在尝试实现用户可以通过渠道订阅通知的场景。

例如,用户可以通过电子邮件、短信、实时通知等方式订阅通知。

目前,为了实现电子邮件通知,我正在IRealTimeNotifier以这种方式实现:

public class EmailNotifier : IRealTimeNotifier, ITransientDependency
{
    private readonly IEmailSender _emailSender;
    private readonly ISettingManager _settingManager;

    public IAbpSession AbpSession { get; set; }

    public EmailNotifier(IEmailSender emailSender, ISettingManager settingManager, IAbpSession abpSession)
    {
        this._emailSender = emailSender;
        this._settingManager = settingManager;
        this.AbpSession = NullAbpSession.Instance;
    }

    public async Task SendNotificationsAsync(UserNotification[] userNotifications)
    {
        List<Task> sendEmails = new List<Task>();
        string fromAddress = this._settingManager.GetSettingValueForTenant(EmailSettingNames.Smtp.UserName, Convert.ToInt32(this.AbpSession.TenantId));

        foreach (UserNotification notification in userNotifications)
        {
            notification.Notification.Data.Properties.TryGetValue("toAddresses", out object toAddresses);
            notification.Notification.Data.Properties.TryGetValue("subject", out object sub);
            notification.Notification.Data.Properties.TryGetValue("body", out object content);
            notification.Notification.Data.Properties.TryGetValue("toAddresses", out object toAddresses);

            List<string> toEmails = toAddresses as List<string>                    ;
            string subject = Convert.ToString(sub);
            string body = Convert.ToString(content);
            toEmails.ForEach(to =>
            {
                sendEmails.Add(this._emailSender.SendAsync(fromAddress, to, subject, body));
            });
        }

        await Task.Run(() =>
        {
            sendEmails.ForEach(async task =>
            {
                try
                {
                    await task.ConfigureAwait(false);
                }
                catch (Exception)
                {
                    // TODO #1: Add email to background job to be sent later.
                    // TODO #2: Add circuit breaker to email sending mechanism
                    /*
                        Email sending is failing because of two possible reasons.
                        1. Email addresses are wrong.
                        2. SMTP server is down.
                        3. Break the circuit while the SMTP server is down.
                     */

                    // TODO #3 (Optional): Notify tenant admin about failure.
                    // TODO #4: Remove throw statement for graceful degradation.
                    throw;
                }
            });
        });
    }
}

问题在于IAbpSession,无论我是通过属性还是构造函数注入它,在执行此通知程序时,响应已经返回并且TenantId在此会话中是null因此发送的电子邮件是主机配置而不是租户配置.

同样,我需要IRealTimeNotifier为 SMS 实现。我想我可以重用SignalRRealTimeNotifier来自 ABP 的,但是tenantIdin 会话被设置为null.

这是发布者被调用的地方:

public class EventUserEmailer : IEventHandler<EntityCreatedEventData<Event>>
{
    public ILogger Logger { get; set; }

    private readonly IEventManager _eventManager;
    private readonly UserManager _userManager;
    private readonly IAbpSession _abpSession;
    private readonly INotificationPublisher _notiticationPublisher;

    public EventUserEmailer(
        UserManager userManager,
        IEventManager eventManager,
        IAbpSession abpSession,
        INotificationPublisher notiticationPublisher)
    {
        _userManager = userManager;
        _eventManager = eventManager;
        _notiticationPublisher = notiticationPublisher;
        _abpSession = abpSession;
        Logger = NullLogger.Instance;
    }

    [UnitOfWork]
    public virtual void HandleEvent(EntityCreatedEventData<Event> eventData)
    {
        // TODO: Send email to all tenant users as a notification

        var users = _userManager
            .Users
            .Where(u => u.TenantId == eventData.Entity.TenantId)
            .ToList();

        // Send notification to all subscribed uses of tenant
        _notiticationPublisher.Publish(AppNotificationNames.EventCreated);
    }
}

有人可以推荐更好的方法吗?或者指出我们在这里做错了什么。

我还没有考虑过如何处理这些特定通知器的 DI。出于测试目的,我在我的模块中给出了一个命名注入,如下所示:

IocManager.IocContainer.Register(
    Component.For<IRealTimeNotifier>()
        .ImplementedBy<EmailNotifier>()
        .Named("Email")
        .LifestyleTransient()
);

当前 ABP 版本:3.7.1

这是我到现在为止的信息。有什么需要的可以在评论中提问。

标签: c#sessionnotificationsaspnetboilerplate

解决方案


// Send notification to all subscribed uses of tenant
_notificationPublisher.Publish(AppNotificationNames.EventCreated);

If you publish notifications like this, ABP's default implementation enqueues a background job.

There is no session in a background job.

You can get the tenantId like this:

var tenantId = userNotifications[0].Notification.TenantId;

using (AbpSession.Use(tenantId, null))
{
    // ...
}

推荐阅读