Activities of "trendline"

  • Exception message and full stack trace: Added a lpx-menu-item on the siderbar, useing abp ajax call the sever data, then update the badge DOM with state and count, visual DOM generated but cannot update the DOM
  • Steps to reproduce the issue: below is a code change plan :

用户需求

扩展project.Web 顶部工具栏 NotificationBadge 的提示铃铛,使其点击后展开一个通知下拉面板。

产品概述

将现有的“铃铛 + 数字徽章(轮询未读数)”改造为 LeptonX 风格的 lpx-context-menu 下拉通知中心:铃铛仅负责切换面板,面板内动态加载并展示最新的 4 条私信通知,按日期分组(TODAY / YESTERDAY / 具体日期),底部提供“See All Notifications”按钮跳转至完整的私信列表页 /notification/private-messages。

核心功能

  • 铃铛图标保留未读数字徽章与现有 30 秒轮询逻辑(沿用 api/notification/private-message/unread-count)。
  • 铃铛点击切换展开/收起 LeptonX 通知下拉面板(不再直接跳转)。
  • 面板首次展开时通过 abp.ajax 调用 api/notification/private-message(MaxResultCount=4,Sorting=CreationTime DESC)动态拉取最新 4 条私信。
  • 拉取到的条目按 CreationTime 在客户端换算为本地日期并分组为 TODAY / YESTERDAY / 具体日期区块渲染。
  • 单条通知项仅作展示,不跳转、不触发标记已读;无头像数据时使用 bi-bell-fill 成员图标样式,Title 作为高亮文本、Content 作为正文、日期/时间作为小字。
  • 顶部显示“Notification [N] NEW”标题与关闭按钮,底部按钮链接至 /notification/private-messages。

技术栈

  • 框架:ASP.NET Core + ABP Framework(LeptonX 主题),Razor 视图(Default.cshtml) + 原生 JavaScript。
  • 样式:复用 LeptonX 主题 lpx-* 类名与 Bootstrap Icons(bi);不引入新依赖、不需前端构建。
  • 数据交互:沿用现有 abp.ajax / abp.event 模式,调用已存在接口。

实现方案

  • 整体策略:仅重写 Default.cshtml 的标记与脚本,将其升级为“铃铛触发 + 下拉面板”结构;视图组件 NotificationBadgeViewComponent.cs 无需改动(仍返回 Default.cshtml)。
  • 关键决策:
  1. 铃铛 <a> 改为 data-lpx-ctx-toggle="notification-bar",移除原 href 跳转,仅切换下拉;跳转职责迁移到底部按钮(<a href="/notification/private-messages"> 或 lpx-button 包链接)。
  2. 下拉面板骨架沿用用户提供的结构:lpx-context-menu + 顶部标题/关闭(data-lpx-close)+ 分组容器(首次为空,由 JS 注入)+ 底部按钮。
  3. 数据条目无头像/发送人字段,采用模板中的“成员图标”样式(lpx-notification-member-icon bi bi-bell-fill),以 Title 作为 lpx-notification-text-highlight、Content 作为正文。
  4. 未读高亮:DTO 无逐条已读标志,依据当前 unreadCount 将前 N 条标记为 unread(提供红色侧边/未读视觉),属合理近似;不触发后端标记已读。
  • 性能与可靠性:列表仅 4 条、按日期分组在客户端 O(n) 完成,开销可忽略;首次展开懒加载,避免无谓请求;复用现有 window.__notificationPollTimer 清理与 abp.domUpdated 重渲染清理逻辑,防止 SPA 下定时器/事件重复绑定与内存泄漏。CreationTime 为 UTC,分组与展示以 new Date(item.creationTime) 的本地日期判断 Today/Yesterday。

实现要点

  • 保留并复用 menu-item-badge 徽章样式与轮询;新增 fetchLatest():组装 URL (abp.appPath||'') + 'api/notification/private-message?maxResultCount=4&sorting=CreationTime%20DESC',abp.ajax GET,成功后将 result.items 渲染至面板。
  • 日期分组函数:将每条 CreationTime 转为本地 Date,与“今天 00:00”“昨天 00:00”比较,归入 TODAY / YESTERDAY / YYYY.MM.DD 三类,按模板生成对应 outer-menu-item + lpx-menu-item-title + lpx-notification-items 区块。
  • 时间文案:当天显示相对时间(如 1h ago)或 HH:mm,非当天显示 HH:mm;可在 JS 内简单格式化。
  • 空态处理:若接口返回 0 条,渲染“暂无通知”占位,避免空白面板。
  • 若 abp 未就绪,沿用现有 setInterval 重试 tryInit 机制初始化下拉加载与轮询。

架构设计

  • 不改变现有工具栏渲染链路:Toolbar → NotificationBadgeViewComponent → Default.cshtml(Razor 输出 HTML + 内联 <script>)。
  • 新增逻辑全部内聚在该 cshtml 的脚本块内,沿用既有 IIFE 与全局清理约定,保持与现有代码一致,无新增模块或接口。

目录结构

apps/web/Project.Web/
└── Components/Toolbar/NotificationBadge/
    └── Default.cshtml                  # [MODIFY] 重写标记:包装为 li#notification-bar + lpx-context-menu 下拉面板(标题/关闭、日期分组容器、底部跳转按钮);脚本增加 fetchLatest() 与日期分组渲染,并接入现有轮询/清理逻辑。
    └── NotificationBadgeViewComponent.cs # [不变] 仅返回 Default.cshtml,无需修改。

设计风格

沿用 ABP LeptonX 主题的通知下拉(lpx-context-menu)视觉规范,保持与现有后台框架一致的现代、简洁、悬浮卡片风格。面板为右上角悬浮的白色/主题色圆角卡片,带轻微阴影与边框,与顶部工具栏铃铛联动展开/收起。

页面/区块设计(Default.cshtml 下拉面板)

  • 顶部标题栏:左侧“Notification”文字 + 未读徽标 lpx-badge(如“2 NEW”),右侧关闭图标(data-lpx-close,点击收起面板),与铃铛徽章未读数联动。
  • 分组通知区:按 TODAY / YESTERDAY / 具体日期分块,每块含 lpx-menu-item-title 分区标题与 lpx-notification-items 列表;每条约 56px 高,左侧 bi-bell-fill 圆形成员图标,右侧 Title(高亮加粗)+ Content(截断单行)+ 时间小字;未读项带左侧色条/背景高亮。
  • 底部操作区:全宽“See All Notifications”按钮(lpx-button),点击跳转 /notification/private-messages 查看完整私信。
  • 交互:悬停条目背景微变,按钮 hover 变色;面板展开带轻微淡入;空态显示“暂无通知”居中提示。整体响应式适配工具栏宽度。

It already got code as below, it is a component view here:.Web\Components\Toolbar\NotificationBadge\Default.cshtml

<li class="outer-menu-item" id="notification-bar"> <a href="javascript:;" class="lpx-menu-item-link" data-lpx-ctx-toggle="notification-bar"> <span class="lpx-menu-item-icon" style="position:relative;"> <i class="lpx-icon action-icon bi bi-bell-fill" aria-hidden="true"></i> <small class="menu-item-badge" id="UnreadCountBadge" data-count="0" style="opacity:0;visibility:hidden;">0</small> </span> </a>

&lt;div class=&quot;lpx-context-menu&quot; data-lpx-context-menu=&quot;notification-bar&quot;&gt;
    &lt;ul class=&quot;lpx-nav-menu-notification&quot; id=&quot;notification-menu&quot;&gt;
        &lt;li class=&quot;outer-menu-item&quot; id=&quot;notification-header&quot;&gt;
            &lt;a class=&quot;lpx-menu-item-link lpx-menu-item&quot;&gt;
                &lt;span class=&quot;lpx-menu-item-icon&quot;&gt;
                    &lt;i class=&quot;lpx-icon outer-icon bi bi-bell-fill&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;
                &lt;/span&gt;
                &lt;span class=&quot;lpx-menu-item-text&quot;&gt;Notification
                    &lt;span class=&quot;lpx-badge&quot; id=&quot;NotificationNewBadge&quot; data-count=&quot;0&quot; style=&quot;opacity:0;visibility:hidden;&quot;&gt;0 NEW&lt;/span&gt;
                &lt;/span&gt;
                &lt;span data-lpx-close=&quot;notification-bar&quot;&gt;
                    &lt;i class=&quot;lpx-icon bi bi-x outer-icon dd-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;

    &lt;div class=&quot;lpx-button-container&quot; id=&quot;notification-footer&quot;&gt;
        &lt;button class=&quot;lpx-button notif-btn-outline&quot; id=&quot;MarkAllAsReadBtn&quot; type=&quot;button&quot;&gt;Mark all as read&lt;/button&gt;
        &lt;a class=&quot;lpx-button notif-btn-primary&quot; href=&quot;/notification/private-messages&quot;&gt;See All Notifications&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

</li>

<script> (function() { 'use strict';

// Clean up previous instance (e.g., after SPA re-render)
if (window.__notificationPollTimer) {
    clearInterval(window.__notificationPollTimer);
    window.__notificationPollTimer = null;
}
if (window.__notificationDomHandler) {
    try { abp.event.off('abp.domUpdated', window.__notificationDomHandler); } catch (e) {}
    window.__notificationDomHandler = null;
}
if (window.__notificationClickHandler) {
    document.removeEventListener('click', window.__notificationClickHandler);
    window.__notificationClickHandler = null;
}

var currentUnread = 0;

// Scoped lookup: avoid stale duplicate-ID elements from previous SPA renders.
function getBadge() {
    var bar = document.getElementById('notification-bar');
    return bar ? bar.querySelector('.menu-item-badge') : null;
}

function getPanel() {
    var bar = document.getElementById('notification-bar');
    return bar ? bar.querySelector('.lpx-context-menu') : null;
}

function isPanelOpen() {
    var p = getPanel();
    return !!(p && p.classList.contains('show'));
}

function unwrap(response) {
    if (response && typeof response === 'object' && 'result' in response) return response.result;
    return response;
}

function esc(s) {
    return String(s == null ? '' : s).replace(/[&&lt;&gt;"']/g, function(c) {
        return { '&': '&amp;', '&lt;&#39;: &#39;&amp;lt;&#39;, &#39;&gt;': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
    });
}

// Use data-count attribute (CSS-driven visibility) instead of display
// manipulation so LeptonX's DOM watcher won't revert our changes.
function updateBadge(count) {
    var num = parseInt(count, 10) || 0;
    currentUnread = num;

    // jQuery lookups — ABP is jQuery-based; avoids stale vanilla references
    var $badge = $('#notification-bar .menu-item-badge');
    if ($badge.length) {
        $badge.attr('data-count', num)
              .text(num > 99 ? '99+' : String(num));
    }

    var $newBadge = $('#NotificationNewBadge');
    if ($newBadge.length) {
        $newBadge.attr('data-count', num > 0 ? num : 0)
                 .text(num > 0 ? num + ' NEW' : '0 NEW');
    }
}

function startOfDay(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); }

function groupLabel(date) {
    var today = startOfDay(new Date());
    var yest = new Date(today);
    yest.setDate(today.getDate() - 1);
    if (date >= today) return 'TODAY';
    if (date >= yest) return 'YESTERDAY';
    var dd = String(date.getDate()).padStart(2, '0');
    var mm = String(date.getMonth() + 1).padStart(2, '0');
    return dd + '.' + mm + '.' + date.getFullYear();
}

function formatTime(date) {
    var hh = String(date.getHours()).padStart(2, '0');
    var mm = String(date.getMinutes()).padStart(2, '0');
    var today = startOfDay(new Date());
    var yest = new Date(today);
    yest.setDate(today.getDate() - 1);
    if (date >= today) return hh + ':' + mm;
    if (date >= yest) return 'Yesterday ' + hh + ':' + mm;
    var dd = String(date.getDate()).padStart(2, '0');
    var mm2 = String(date.getMonth() + 1).padStart(2, '0');
    return mm2 + '/' + dd + ' ' + hh + ':' + mm;
}

function renderGroups(items) {
    var menu = document.getElementById('notification-menu');
    if (!menu) return;

    var old = menu.querySelectorAll('.notif-group');
    for (var i = 0; i &lt; old.length; i++) old[i].remove();

    if (!items || !items.length) {
        var emptyLi = document.createElement(&#39;li&#39;);
        emptyLi.className = &#39;outer-menu-item notif-group&#39;;
        emptyLi.innerHTML = &#39;&lt;div class=&quot;lpx-notification-empty&quot;&gt;暂无通知&lt;/div&gt;';
        menu.appendChild(emptyLi);
        return;
    }

    var groups = [];
    items.forEach(function(it, idx) {
        var d = new Date(it.creationTime);
        var label = groupLabel(d);
        var g = null;
        for (var j = 0; j &lt; groups.length; j++) {
            if (groups[j].label === label) { g = groups[j]; break; }
        }
        if (!g) { g = { label: label, items: [] }; groups.push(g); }
        g.items.push({ data: it, unread: idx &lt; currentUnread, date: d });
    });

    groups.forEach(function(g) {
        var li = document.createElement(&#39;li&#39;);
        li.className = &#39;outer-menu-item notif-group&#39;;
        var html = &#39;&lt;div class=&quot;lpx-menu-item-title&quot;&gt;' + esc(g.label) + '&lt;/div&gt;'
            + '&lt;ul class=&quot;lpx-notification-items lpx-inner-menu&quot;&gt;';
        g.items.forEach(function(it) {
            html += '&lt;li class=&quot;lpx-notification-item&#39; + (it.unread ? &#39; unread&#39; : &#39;&#39;) + &#39;&quot; data-id=&quot;&#39; + esc(it.data.id) + &#39;&quot;&gt;'
                + '&lt;a class=&quot;lpx-notification-item-link&quot;&gt;'
                + '&lt;div class=&quot;lpx-notification-member-container&quot;&gt;&lt;i class=&quot;lpx-notification-member-icon bi bi-bell-fill&quot;&gt;&lt;/i&gt;&lt;/div&gt;'
                + '&lt;div&gt;&lt;p class=&quot;lpx-notification-text&quot;&gt;&lt;span class=&quot;lpx-notification-text-highlight&quot;&gt;' + esc(it.data.title)
                + '&lt;/span&gt; ' + esc(it.data.content) + '&lt;/p&gt;'
                + '&lt;small class=&quot;lpx-notification-text-small&quot;&gt;' + esc(formatTime(it.date)) + '&lt;/small&gt;&lt;/div&gt;'
                + '&lt;/a&gt;&lt;/li&gt;';
        });
        html += '&lt;/ul&gt;';
        li.innerHTML = html;
        menu.appendChild(li);
    });
}

function fetchLatest() {
    if (typeof abp === 'undefined' || !abp || !abp.ajax) return;
    abp.ajax({
        url: (abp.appPath || '') + 'api/notification/private-message?maxResultCount=5&sorting=CreationTime%20DESC',
        type: 'GET',
        success: function(response) {
            var data = unwrap(response);
            var items = (data && data.items) ? data.items : [];
            renderGroups(items);
        },
        error: function() {}
    });
}

function fetchCount() {
    if (typeof abp === 'undefined' || !abp || !abp.ajax) return;
    abp.ajax({
        url: (abp.appPath || '') + 'api/notification/private-message/unread-count',
        type: 'GET',
        success: function(response) {
            var num = parseInt(unwrap(response), 10) || 0;
            updateBadge(num);
            if (isPanelOpen()) fetchLatest();
        },
        error: function() {}
    });
}

function markOneRead(id, liEl) {
    if (typeof abp === 'undefined' || !abp || !abp.ajax) return;
    abp.ajax({
        url: (abp.appPath || '') + 'api/notification/private-message/' + id + '/mark-as-read',
        type: 'POST',
        success: function() {
            if (liEl) liEl.classList.remove('unread');
            updateBadge(Math.max(0, currentUnread - 1));
        },
        error: function() {}
    });
}

function markAllRead() {
    if (typeof abp === 'undefined' || !abp || !abp.ajax) return;
    abp.ajax({
        url: (abp.appPath || '') + 'api/notification/private-message/mark-all-as-read',
        type: 'POST',
        success: function() {
            var menu = document.getElementById('notification-menu');
            if (menu) {
                var unread = menu.querySelectorAll('.lpx-notification-item.unread');
                for (var i = 0; i &lt; unread.length; i++) unread[i].classList.remove(&#39;unread&#39;);
            }
            updateBadge(0);
        },
        error: function() {}
    });
}

function startPolling() {
    if (window.__notificationPollTimer) return;
    fetchCount();
    window.__notificationPollTimer = setInterval(fetchCount, 30000);
}

window.refreshNotificationBadge = fetchCount;
window.refreshNotificationList = fetchLatest;

// Event delegation (survives SPA re-renders)
window.__notificationClickHandler = function(e) {
    if (!e.target || !e.target.closest) return;

    if (e.target.closest(&#39;[data-lpx-ctx-toggle=&quot;notification-bar&quot;]&#39;)) {
        fetchLatest();
        return;
    }

    if (e.target.closest(&#39;#MarkAllAsReadBtn&#39;)) {
        markAllRead();
        return;
    }

    var itemEl = e.target.closest(&#39;.lpx-notification-item&#39;);
    if (itemEl &amp;&amp; itemEl.getAttribute(&#39;data-id&#39;)) {
        markOneRead(itemEl.getAttribute(&#39;data-id&#39;), itemEl);
    }
};
document.addEventListener(&#39;click&#39;, window.__notificationClickHandler);

function tryInit() {
    if (typeof abp === &#39;undefined&#39; || !abp || !abp.ajax) return false;

    window.__notificationDomHandler = fetchCount;
    abp.event.on(&#39;abp.domUpdated&#39;, window.__notificationDomHandler);

    startPolling();
    return true;
}

if (!tryInit()) {
    var attempts = 0;
    var readyTimer = setInterval(function() {
        attempts++;
        if (tryInit()) {
            clearInterval(readyTimer);
        } else if (attempts &gt; 40) {
            clearInterval(readyTimer);
        }
    }, 500);
}

})(); </script>

Virtaul DOM generated, but cannot update the real DOM

  • Template: microservice
  • Created ABP Studio Version: 2.2.4
  • Current ABP Studio Version: 2.2.4
  • Multi-Tenancy: Yes
  • UI Framework: mvc
  • Theme: leptonx
  • Theme Style: system
  • Theme Menu Placement: side
  • Database Provider: ef
  • Database Management System: sqlserver
  • Mobile Framework: maui
  • Public Website: Yes
  • Social Login: Yes
  • Include Tests: Yes
  • Dynamic Localization: Yes
  • Kubernetes Configuration: Yes
  • Grafana Dashboard: Yes
  • Use Local References: No
  • Aspire: No
  • Optional Modules:
    • GDPR
    • TextTemplateManagement
    • AuditLogging
    • OpenIddictAdmin
  • Selected Languages: Chinese (Simplified), Chinese (Traditional), English
  • AI Management Enabled: Yes
  • Selected AI Providers: Ollama, OpenAI
  • Default Language: Chinese (Simplified)
  • Exception message and full stack trace: After initial running the solution, Language Management Module and AI Management Module permissions not seeded the initialed permission grants in database
  • Steps to reproduce the issue: 1, running all services; 2, running web gateway; 3, running web app; 4 running auth server app; 5, browse the web app;

ABP ver 9.3.5 with micro-services architecture, the UserFriendlyException not shown on the razor page client when backend API throw a UserFriendlyException, below is the code section: A BusinessException threw but it cannot shown on the client side in a application service like below: `
public virtual async Task

        if (!result)
        {
            throw new InvalidVerificationCodeException();
        }

        var identityUser = await _phoneNumberLoginNewUserCreator.CreateAsync(input.PhoneNumber, input.UserName, input.Password, input.EmailAddress);

        return ObjectMapper.Map<IdentityUser, IdentityUserDto>(identityUser);
    }
    `

the InvalidVerificationCodeException is a BusinessException

I added an UserFriendlyException in the Controller like below: `
[HttpPost] [Route("register/by-phone-number")] public virtual async Task

            return result;
        }
        catch (BusinessException ex)
        {

            throw new UserFriendlyException(ex.Code);
        }
    }
    `
    it got below logs in the backend:
    `
    2025-10-08 17:38:45.887 +08:00 [WRN] ---------- RemoteServiceErrorInfo ----------

{ "code": null, "message": "EasyAbp.Abp.PhoneNumberLogin:InvalidVerificationCode", "details": null, "data": {}, "validationErrors": null }

2025-10-08 17:38:45.887 +08:00 [WRN] EasyAbp.Abp.PhoneNumberLogin:InvalidVerificationCode Volo.Abp.UserFriendlyException: EasyAbp.Abp.PhoneNumberLogin:InvalidVerificationCode at EasyAbp.Abp.PhoneNumberLogin.Web.Controllers.PhoneNumberLogin.Account.LoginController.RegisterByPhoneNumberAsync(RegisterWithPhoneNumberInput input) at lambda_method3691(Closure, Object) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.

then the client get a 403 forbidden HTTP Status code with the errors like below, it doesn't use UI throw a message: { "error": { "code": null, "message": "EasyAbp.Abp.PhoneNumberLogin:InvalidVerificationCode", "details": null, "data": {}, "validationErrors": null } }

  • Exception message and full stack trace:2025-09-28 05:01:50.206 +08:00 [FTL] Host terminated unexpectedly! Volo.Abp.AbpInitializationException: An error occurred during the initialize Volo.Abp.Modularity.OnApplicationInitializationModuleLifecycleContributor phase of the module Volo.Abp.AuditLogging.AbpAuditLoggingApplicationModule, Volo.Abp.AuditLogging.Application, Version=9.3.4.0, Culture=neutral, PublicKeyToken=null: Unexpected end of expression.. See the inner exception for details. ---> System.FormatException: Unexpected end of expression. at Quartz.CronExpression.BuildExpression(String expression) at Quartz.CronExpression..ctor(String cronExpression) at Quartz.CronExpression.ValidateExpression(String cronExpression) at Quartz.CronScheduleBuilder.CronSchedule(String cronExpression) at Quartz.CronScheduleTriggerBuilderExtensions.WithCronSchedule(TriggerBuilder triggerBuilder, String cronExpression) at Volo.Abp.BackgroundWorkers.Quartz.QuartzPeriodicBackgroundWorkerAdapter1.BuildWorker(IBackgroundWorker worker) at Volo.Abp.BackgroundWorkers.Quartz.QuartzBackgroundWorkerManager.ReScheduleJobAsync(IBackgroundWorker worker, CancellationToken cancellationToken) at Volo.Abp.BackgroundWorkers.Quartz.QuartzBackgroundWorkerManager.AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken) at Volo.Abp.AuditLogging.AbpAuditLoggingApplicationModule.OnApplicationInitializationAsync(ApplicationInitializationContext context) at Volo.Abp.Modularity.OnApplicationInitializationModuleLifecycleContributor.InitializeAsync(ApplicationInitializationContext context, IAbpModule module) at Volo.Abp.Modularity.ModuleManager.InitializeModulesAsync(ApplicationInitializationContext context) --- End of inner exception stack trace --- at Volo.Abp.Modularity.ModuleManager.InitializeModulesAsync(ApplicationInitializationContext context) at Volo.Abp.AbpApplicationBase.InitializeModulesAsync() at Volo.Abp.AbpApplicationWithExternalServiceProvider.InitializeAsync(IServiceProvider serviceProvider) at Microsoft.AspNetCore.Builder.AbpApplicationBuilderExtensions.InitializeApplicationAsync(IApplicationBuilder app) at Viewtance.FM.Program.Main(String[] args) in D:\Source\Repos\apps\Viewtance.FM\src\Viewtance.FM.HttpApi.Host\Program.cs:line 58 `
  • Steps to reproduce the issue: Debug Host project in a tired solution, host project failed to start.
  • Exception message and full stack trace: cannot generate the proxies for new added integration services in a microservice project
  • Steps to reproduce the issue: follow the guides Integration Services add interface and implementation for the new integration service, when using abp studio or cli generate the proxy, not proxies generated, the integration service provider microservice is a project template service IdentityService

any miss configuration for integration service, or the generate proxy action is wrong?

I want to implement some actions (such as given award points to a user daily if the user sign in every day), I need to add an event to OpenIddict server event that it could fire an event handler to implement the logistics, I have followed the article add the event handler and registered in AuthServer project (a micro-services architecture solution), but the event handler not fired and no logs logged, is there any suggestions to debug, or I am using an incorrect way to configure the event handler?

  • Exception message and full stack trace: No
  • Steps to reproduce the issue: Create an OpenIddictServerEvents.ProcessSignInContext event handler and registered, some code as below:
public class DailyPointsHandler : IOpenIddictServerHandler<OpenIddictServerEvents.ProcessSignInContext>//, ITransientDependency
{
    public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.ProcessSignInContext>()
    .UseScopedHandler<DailyPointsHandler>()
    .SetOrder(100_000)
    .SetType(OpenIddictServerHandlerType.Custom)
    .Build();

    private readonly IdentityUserManager _userManager;
    private readonly IClock _clock;
    private readonly ILogger<DailyPointsHandler> _logger;
    private readonly IRewardPointsHistoriesAppService _rewardPointsHistoriesAppService;
    private readonly IUnitOfWorkManager _unitOfWorkManager;

    public DailyPointsHandler(
        IdentityUserManager userManager,
        IClock clock,
        ILogger<DailyPointsHandler> logger,
        IRewardPointsHistoriesAppService rewardPointsHistoriesAppService,
        IUnitOfWorkManager unitOfWorkManager)
    {
        _userManager = userManager;
        _clock = clock;
        _logger = logger;
        _rewardPointsHistoriesAppService = rewardPointsHistoriesAppService;
        _unitOfWorkManager = unitOfWorkManager;

        _logger.LogInformation("DailyPointsHandler constructor initialized");
    }

    public async ValueTask HandleAsync(OpenIddictServerEvents.ProcessSignInContext context)
    {
        _logger.LogInformation("进入每日积分处理器");

        if (context is null) throw new ArgumentNullException(nameof(context));

        // 跳过客户端凭证流程和刷新令牌流程
        if (context.Request.IsClientCredentialsGrantType() ||
            context.Request.IsRefreshTokenGrantType())
        {
            _logger.LogInformation("跳过非用户登录流程");
            return;
        }

        // 获取用户ID
        var userId = context.Principal?.FindFirst(OpenIddictConstants.Claims.Subject)?.Value;
        if (string.IsNullOrEmpty(userId))
        {
            _logger.LogWarning("无法从声明中获取用户ID");
            return;
        }

        try
        {
            using var uow = _unitOfWorkManager.Begin(requiresNew: true);

            var user = await _userManager.FindByIdAsync(userId);
            if (user == null)
            {
                _logger.LogWarning("用户未找到: {UserId}", userId);
                return;
            }

            if (user != null)
            {
                var rewardPointsHistoryCreate = new RewardPointsHistoryCreateDto();
                rewardPointsHistoryCreate.UserId = user.Id;
                rewardPointsHistoryCreate.RewardPointType = RewardPointType.DailySignIn;
                await _rewardPointsHistoriesAppService.CreateAsync(rewardPointsHistoryCreate);
                await uow.CompleteAsync();
                _logger.LogInformation("用户 {UserName} 获得每日登录奖励", user.UserName);
            }

        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "每日积分赠送处理失败");
            // 失败时继续登录流程,不影响认证
        }

    }

}

registered it at:

  private void PreConfigureOpenIddict(IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
    {
        PreConfigure<OpenIddictBuilder>(builder =>
        {
            builder.AddValidation(options =>
            {
                options.AddAudiences("AuthServer");
                options.UseLocalServer();
                options.UseAspNetCore();
            });
            builder.AddServer(options =>
            {
                options.AddEventHandler<ProcessSignInContext>(builder =>
                    builder.UseInlineHandler(context =>
                    {
                        // Attach custom metadata to the configuration document.
                        //context.Metadata["custom_metadata"] = 42;
                        context.Logger.LogInformation("Processing sign-in event in inline handler for user {userId}.", context.Principal?.FindFirst(OpenIddictConstants.Claims.Subject)?.Value);

                        return default;
                    }));
            });
        });

        PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
        {
            serverBuilder.AddEventHandler(DailyPointsHandler.Descriptor);
            serverBuilder.AddEventHandler(SignOutEventHandler.Descriptor);
            serverBuilder.AddEventHandler<ProcessSignInContext>(builder =>
                builder.UseInlineHandler(context =>
                {
                    // Attach custom metadata to the configuration document.
                    //context.Metadata["custom_metadata"] = 42;
                    context.Logger.LogInformation("Processing sign-in event in inline handler for user {userId}.", context.Principal?.FindFirst(OpenIddictConstants.Claims.Subject)?.Value);

                    return default;
                }));
        });
        }

Exception message and full stack trace: Navigation menu does not appear on specified language setting

Steps to reproduce the issue:

1 login with an account, back to the web

2 the navigation menus doesn't appear

3 if access the url directly then it will jump to the account login page let you login again

4 if change the language to another one, the navigation appears, if changed the language back the navigation menu disappeared again

5 if waiting for a moments(about 10 minutes around), the navigation menu may be appears

I have tried restart the application and the server, cleared the redis server cache, it cannot resolve it. I have 3 languages configured, english, zh-hant, zh-hans, the issue occurred on zh-hans.

Microsoft.AspNetCore.Authentication.AuthenticationFailureException: An error was encountered while handling the remote login. ---> Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: Message contains error: 'invalid_grant', error_description: 'The specified token is invalid.', error_uri: 'https://documentation.openiddict.com/errors/ID2004'. at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest) at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync() --- End of inner exception stack trace --- at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync() at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Prometheus.HttpMetrics.HttpRequestDurationMiddleware.Invoke(HttpContext context) at Prometheus.HttpMetrics.HttpRequestCountMiddleware.Invoke(HttpContext context) at Prometheus.HttpMetrics.HttpInProgressMiddleware.Invoke(HttpContext context) at Volo.Abp.AspNetCore.Security.AbpSecurityHeadersMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<

  • Steps to reproduce the issue: A micro service architecture solution, from a web or a public web redirect to a separated OpenIddict AuthServer, after the authority, redirected to the original the url, occurred this error

With Abp 9.2.1, abp suite 9.2.1

  • Exception message and full stack trace: === ENTITY GENERATION STARTED ===
  • SOLUTION: Viewtance.PointLink.AdministrationService
  • ENTITY:{ "Id": "1f31352e-9316-44e6-8d09-b4cd6812deb4", "Name": "TenantDomain", "OriginalName": "TenantDomain", "NamePlural": "TenantDomains", "DatabaseTableName": "TenantDomains", "Namespace": "TenantDomains", "Type": 1, "MasterEntityName": null, "MasterEntity": null, "BaseClass": "FullAuditedEntity", "PageTitle": "TenantDomains", "MenuIcon": "file-alt", "PrimaryKeyType": "Guid", "PreserveCustomCode": true, "IsMultiTenant": true, "CheckConcurrency": true, "BulkDeleteEnabled": true, "ShouldCreateUserInterface": true, "ShouldCreateBackend": true, "ShouldExportExcel": true, "ShouldAddMigration": false, "ShouldUpdateDatabase": false, "CreateTests": true, "Properties": [ { "Id": "8c6af49d-8a07-4d1b-9c7b-c88f7305c670", "Name": "DomainName", "Type": "string", "EnumType": "", "EnumNamespace": "", "EnumAngularImport": "shared/enums", "EnumFilePath": null, "DefaultValue": null, "IsNullable": false, "IsRequired": true, "IsFilterable": true, "AllowEmptyStrings": false, "IsTextArea": false, "MinLength": 1, "MaxLength": 128, "SortOrder": 0, "SortType": 0, "Regex": "", "EmailValidation": false, "ShowOnList": true, "ShowOnCreateModal": true, "ShowOnEditModal": true, "ReadonlyOnEditModal": false, "EnumValues": null, "IsSelected": true, "MaxFileSize": null, "OrdinalIndex": 0 }, { "Id": "66956a19-def1-49f7-9525-6b085f48121f", "Name": "DomainType", "Type": "enum", "EnumType": "DomainType", "EnumNamespace": "Viewtance.PointLink.AdministrationService.Tenants", "EnumAngularImport": "shared/enums/domain-type", "EnumFilePath": "/Viewtance.PointLink.AdministrationService.Contracts/Tenants/DomainType.cs", "DefaultValue": null, "IsNullable": false, "IsRequired": false, "IsFilterable": true, "AllowEmptyStrings": false, "IsTextArea": false, "MinLength": null, "MaxLength": null, "SortOrder": 0, "SortType": 0, "Regex": "", "EmailValidation": false, "ShowOnList": true, "ShowOnCreateModal": true, "ShowOnEditModal": true, "ReadonlyOnEditModal": false, "EnumValues": { "Console": 1, "AuthServer": 2, "Public": 3, "Mobile": 4 }, "IsSelected": true, "MaxFileSize": null, "OrdinalIndex": 0 }, { "Id": "29e4f2bc-97f2-4682-8695-ba1ffbe47316", "Name": "DomainTenantId", "Type": "Guid", "EnumType": "", "EnumNamespace": "", "EnumAngularImport": "shared/enums", "EnumFilePath": null, "DefaultValue": null, "IsNullable": false, "IsRequired": false, "IsFilterable": true, "AllowEmptyStrings": false, "IsTextArea": false, "MinLength": null, "MaxLength": null, "SortOrder": 0, "SortType": 0, "Regex": "", "EmailValidation": false, "ShowOnList": true, "ShowOnCreateModal": true, "ShowOnEditModal": true, "ReadonlyOnEditModal": false, "EnumValues": null, "IsSelected": false, "MaxFileSize": null, "OrdinalIndex": 0 } ], "NavigationProperties": [], "NavigationConnections": [], "ChildEntities": [], "PhysicalFileName": "TenantDomain.json" }

2025-07-13 04:57:07.493 +08:00 [INF] 1/10 - EntityGenerateCommand started... 2025-07-13 04:57:07.948 +08:00 [INF] 1/10 - EntityGenerateCommand completed. | Duration: 454 ms. 2025-07-13 04:57:07.948 +08:00 [INF] 2/10 - RepositoryCommand started... 2025-07-13 04:57:08.740 +08:00 [INF] 2/10 - RepositoryCommand completed. | Duration: 791 ms. 2025-07-13 04:57:08.740 +08:00 [INF] 3/10 - ManagerCommand started... 2025-07-13 04:57:08.988 +08:00 [INF] 3/10 - ManagerCommand completed. | Duration: 247 ms. 2025-07-13 04:57:08.988 +08:00 [INF] 4/10 - AppServiceCommand started... 2025-07-13 04:57:13.792 +08:00 [INF] 4/10 - AppServiceCommand completed. | Duration: 4804 ms. 2025-07-13 04:57:13.792 +08:00 [INF] 5/10 - PermissionCommand started... 2025-07-13 04:57:13.849 +08:00 [INF] 5/10 - PermissionCommand completed. | Duration: 56 ms. 2025-07-13 04:57:13.849 +08:00 [INF] 6/10 - ApplicationObjectMappingCommand started... 2025-07-13 04:57:13.878 +08:00 [ERR] ---------- RemoteServiceErrorInfo ---------- { "code": null, "message": "An internal error occurred during your request!", "details": null, "data": null, "validationErrors": null }

2025-07-13 04:57:13.879 +08:00 [ERR] Value cannot be null. (Parameter 'path') System.ArgumentNullException: Value cannot be null. (Parameter 'path') at System.ArgumentNullException.Throw(String paramName) at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName) at System.ArgumentException.ThrowNullOrEmptyException(String argument, String paramName) at System.IO.File.ReadAllTextAsync(String path, CancellationToken cancellationToken) at Volo.Abp.Suite.Areas.AbpSuite.CrudPageGenerator.Commands.ApplicationObjectMappingCommand.IJBgcTmZJf() at Volo.Abp.Suite.Areas.AbpSuite.CrudPageGenerator.Commands.ApplicationObjectMappingCommand.ExecuteAsync(CrudPageCommandOptions options) at Volo.Abp.Suite.Areas.AbpSuite.CrudPageGenerator.CommandManager.ExecuteAllAsync(CrudPageCommandOptions options) at Volo.Abp.Suite.Areas.AbpSuite.CrudPageGenerator.Commands.CrudPageGenerator.GenerateAsync(EntityModel entity, Solution solution, EntityModel masterEntity, List1 navigationConnections) at Volo.Abp.Suite.Controllers.CrudPageGeneratorController.SaveAndGenerateEntityAsync(Guid solutionId, EntityModel entity) at lambda_method2003(Closure, Object) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) 2025-07-13 04:57:13.884 +08:00 [INF] Executing ObjectResult, writing value of type 'Volo.Abp.Http.RemoteServiceErrorResponse'. 2025-07-13 04:57:13.885 +08:00 [INF] Executed action Volo.Abp.Suite.Controllers.CrudPageGeneratorController.SaveAndGenerateEntityAsync (Volo.Abp.Suite) in 62760.4142ms 2025-07-13 04:57:13.885 +08:00 [INF] Executed endpoint 'Volo.Abp.Suite.Controllers.CrudPageGeneratorController.SaveAndGenerateEntityAsync (Volo.Abp.Suite)' 2025-07-13 04:57:13.885 +08:00 [INF] Request finished HTTP/1.1 POST http://localhost:3000/api/abpSuite/crudPageGenerator/6f93a03f-284a-4842-ae53-00aec0831ca6/save-and-generate-entity - 500 null application/json; charset=utf-8 62761.8723ms

  • Steps to reproduce the issue: Configured a Entity as below logs described, generate the CRUD page
  • Exception message and full stack trace: Navigation menu does not appear on specified language setting
  • Steps to reproduce the issue:
    • 1 login with an account, back to the web
    • 2 the navigation menus doesn't appear
    • 3 if access the url directly then it will jump to the account login page let you login again
    • 4 if change the language to another one, the navigation appears, if changed the language back the navigation menu disappeared again
    • 5 if waiting for a moments(about 10 minutes around), the navigation menu may be appears

I have tried restart the application and the server, cleared the redis server cache, it cannot resolve it. I have 3 languages configured, english, zh-hant, zh-hans, the issue occurred on zh-hans.

Showing 1 to 10 of 23 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on October 21, 2026, 11:09
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.