Activities of "maliming"

Hi,

You're right — the setters on CreationAuditedAggregateRoot<TKey> (inherited by AuditedAggregateRoot<TKey>) are public, while the ones on CreationAuditedEntity<TKey> are protected. So on AuditedEntity<TKey> you can't assign CreatorId / CreationTime from outside the entity.

For your case, use ObjectHelper.TrySetProperty — it's the same helper ABP's own AuditPropertySetter uses internally for these fields, so it works through reflection on the protected setters:

using Volo.Abp;

ObjectHelper.TrySetProperty(entity, x => x.CreatorId, () => user.Id);
ObjectHelper.TrySetProperty(entity, x => x.CreationTime, () => Clock.Now);

One thing worth knowing: ABP's auto-auditing only fills CreationTime / CreatorId when they're still at their default value (default(DateTime) and null). So if you set them yourself before SaveChangesAsync, ABP won't overwrite them — handy for data imports or creating records on behalf of another user.

Thanks

Hi,

Thanks for the detailed write-up. Quick confirmation first: I checked the v9.2.4 source and the current 10.x code path — the controller and the underlying app service are unchanged, so what you reported is by design rather than a regression.

The reason both endpoints are anonymous is that the framework's RemoteTenantStore (used by MVC, Blazor, MAUI and the Angular tenant-box) needs to resolve a tenant by name or id before the user logs in — it has nothing to authenticate with at that point. So we can't simply add [Authorize] to the default endpoints without breaking those clients.

For your case, since the Angular SPA already gets the tenant from the login URL query parameter, you can replace the IAbpTenantAppService implementation in your *.HttpApi.Host (and the AuthServer host too, if it exposes the same routes in your tiered setup) so both methods return a constant "not found" response. That removes the existence oracle without breaking the URL contract:

using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.AspNetCore.Mvc.MultiTenancy;
using Volo.Abp.DependencyInjection;

namespace MyCompanyName.MyProjectName;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IAbpTenantAppService))]
public class SealedAbpTenantAppService : ApplicationService, IAbpTenantAppService
{
    public virtual Task<FindTenantResultDto> FindTenantByNameAsync(string name)
    {
        return Task.FromResult(new FindTenantResultDto { Success = false });
    }

    public virtual Task<FindTenantResultDto> FindTenantByIdAsync(Guid id)
    {
        return Task.FromResult(new FindTenantResultDto { Success = false });
    }
}

With this in place, both tenants/by-name/{name} and tenants/by-id/{id} return the same payload for every input:

{"success":false,"tenantId":null,"name":null,"normalizedName":null,"isActive":false}

So the response no longer differs for existing vs non-existing tenants and the enumeration is gone. I verified this on a fresh 10.x app with a seeded tenant — same bytes in both cases.

For the missing rate limit, we'd rather not bake one into the framework either, since the right threshold depends on your deployment. The simplest options are ASP.NET Core's built-in RateLimiter middleware scoped to /api/abp/multi-tenancy/tenants/*, or a rule at your reverse proxy (YARP / nginx / ingress). Either works alongside the override above.

Thanks

Hi,

AI Management is host-level by design today — workspaces, MCP connections, and indexed knowledge aren't tenant-scoped entities, so they're shared across tenants.

Multi-tenancy is on our roadmap and we're targeting the 10.6 line. The scope covers tenant-owned workspaces, per-tenant usage and limits, and a migration path. The work is split into several phases, so the exact release version may still shift.

Thanks

Hi,

Yes, this is a regression in LoadingDirective introduced in 10.2.0 when it was switched from ComponentFactoryResolver.resolveComponentFactory(...).create() (which only creates the component) to ViewContainerRef.createComponent() (which also auto-inserts the host element into the DOM right next to the directive anchor). When [abpLoading]="false" is bound from the start and never toggled, the directive still creates LoadingComponent on its first effect run, so the auto-inserted spinner sits in the DOM forever. As soon as you toggle once, the existing cleanup paths capture rootNode and the directive works correctly again — exactly what you observed.

The fix is already on the way in https://github.com/abpframework/abp/pull/25578 (targeting rel-10.4). Until it ships, you can drop in this replacement directive — same selector [abpLoading], so usage in your templates stays unchanged. Import it in each standalone component that uses [abpLoading] (instead of importing LoadingDirective from @abp/ng.theme.shared):

import {
  ComponentRef, Directive, ElementRef, EmbeddedViewRef, Injector,
  OnDestroy, OnInit, Renderer2, effect, inject, input, ViewContainerRef
} from '@angular/core';
import { Subscription, timer } from 'rxjs';
import { take } from 'rxjs/operators';
import { LoadingComponent } from '@abp/ng.theme.shared';

@Directive({
  selector: '[abpLoading]',
  host: { '[style.position]': '"relative"' }
})
export class FixedLoadingDirective implements OnInit, OnDestroy {
  private elRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private injector = inject(Injector);
  private renderer = inject(Renderer2);
  private viewContainerRef = inject(ViewContainerRef);

  readonly loading = input(false, { alias: 'abpLoading' });
  readonly targetElementInput = input<HTMLElement | undefined>(undefined, { alias: 'abpLoadingTargetElement' });
  readonly delay = input(0, { alias: 'abpLoadingDelay' });

  private targetElement: HTMLElement | undefined;

  componentRef: ComponentRef<LoadingComponent> | null = null;
  rootNode: HTMLDivElement | null = null;
  timerSubscription: Subscription | null = null;

  constructor() {
    effect(() => this.handleLoadingChange(this.loading()));
  }

  private handleLoadingChange(newValue: boolean) {
    setTimeout(() => {
      if (!newValue) {
        this.clearLoading();
        return;
      }

      if (this.timerSubscription) {
        this.timerSubscription.unsubscribe();
      }

      this.timerSubscription = timer(this.delay())
        .pipe(take(1))
        .subscribe(() => {
          if (!this.loading()) return;

          if (!this.componentRef) {
            this.componentRef = this.viewContainerRef.createComponent(LoadingComponent, {
              injector: this.injector
            });
          }

          if (!this.rootNode) {
            this.rootNode = (this.componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0];
            this.targetElement?.appendChild(this.rootNode as HTMLDivElement);
          }

          this.timerSubscription = null;
        });
    }, 0);
  }

  private clearLoading() {
    if (this.timerSubscription) {
      this.timerSubscription.unsubscribe();
      this.timerSubscription = null;
    }
    if (this.rootNode?.parentElement) {
      this.renderer.removeChild(this.rootNode.parentElement, this.rootNode);
      this.rootNode = null;
    }
  }

  ngOnInit() {
    this.targetElement = this.targetElementInput();
    if (!this.targetElement) {
      const { offsetHeight, offsetWidth } = this.elRef.nativeElement;
      if (!offsetHeight && !offsetWidth && this.elRef.nativeElement.children?.length) {
        this.targetElement = this.elRef.nativeElement.children[0] as HTMLElement;
      } else {
        this.targetElement = this.elRef.nativeElement;
      }
    }
  }

  ngOnDestroy() {
    this.clearLoading();
  }
}

Usage in a standalone component:

@Component({
  selector: 'my-page',
  imports: [FixedLoadingDirective],
  template: `&lt;div [abpLoading]=&quot;loading()&quot;&gt;...&lt;/div&gt;`
})
export class MyPageComponent { ... }

Two things to note:

  • Don't import both LoadingDirective (from @abp/ng.theme.shared) and FixedLoadingDirective in the same component — Angular will complain about the duplicate [abpLoading] selector. Pick one per component.
  • This only affects components where you apply the fix. ABP's own components that use [abpLoading] internally always toggle the value, so they don't hit this issue — you don't need to patch them.

Once https://github.com/abpframework/abp/pull/25578 is merged and released, you can delete FixedLoadingDirective and go back to importing LoadingDirective from @abp/ng.theme.shared.

Thanks

Hi,

Quick answers to your three points:

ABP commercial license scope: our license covers our own Volo.* modules only — it doesn't bundle any third-party component license. The one exception is Blazorise: we have an agreement with Megabit, so every ABP commercial customer automatically gets a Blazorise PRO license at no extra cost. Sign in (or register) at https://blazorise.com/account/user/manage/license using the same email as your abp.io account to get your license key and Product Token. Full steps are in https://abp.io/support/questions/9912.

Redis: the concern is outdated. Here's the timeline:

  • Redis ≤ 7.2.4 — BSD-3-Clause (open source)
  • Redis 7.4 / 7.x (from March 2024) — RSALv2 + SSPLv1 dual-license. SSPL primarily restricts cloud providers re-packaging Redis as a managed service.
  • Redis 8.0+ (released May 2025) — tri-licensed under RSALv2 + SSPLv1 + AGPLv3. AGPLv3 is OSI-approved open source, so you can use Redis 8.0+ commercially under AGPLv3 with no payment required.

Refs: https://redis.io/blog/agplv3/ and https://redis.io/legal/licenses/.

Also worth noting — the client library we ship is StackExchange.Redis, which is MIT-licensed and independent from the Redis server license.

If you'd still rather not depend on Redis, you can — ABP only depends on the IDistributedCache abstraction, so any compatible implementation works as a drop-in replacement.

Other built-in libraries: the third-party packages we pull in by default are all under permissive licenses — MIT, Apache 2.0, or BSD-3-Clause (e.g. Newtonsoft.Json, AutoMapper, OpenIddict, Serilog). ABP code is written against interfaces and common abstractions, so if any third-party we depend on ever introduces a license that doesn't fit a commercial customer, we'd switch to an open-source alternative before you hit a problem.

Thanks

Hi,

Found it. Your OnPostAsync calls _userManager.ChangePasswordAsync(...) without calling IdentityOptions.SetAsync() first, so ASP.NET Core Identity validates the password against the framework default PasswordOptions (length=6, requires digit/lower/upper/non-alphanumeric) instead of the values from AbpSettings. That's exactly why the page shows the old "6 characters" errors no matter what you save.

IdentityOptions.SetAsync() reads the current Abp.Identity.Password.* settings (respecting tenant scope) and pushes them into IdentityOptions.Password, so UserManager.ChangePasswordAsync validates against the values you saved. Inject IOptions<IdentityOptions> and call SetAsync() inside the CurrentTenant.Change scope, before ChangePasswordAsync:

private readonly IOptions<IdentityOptions> _identityOptions;

public ChangePasswordModel(
    UserManager<IdentityUser> userManager,
    SignInManager<IdentityUser> signInManager,
    IIdentityUserRepository identityUserRepository,
    IConfiguration configuration,
    ITenantRepository tenantRepository,
    IEmailNotificationAppService emailService,
    ILogger<ChangePasswordModel> logger,
    IOptions<IdentityOptions> identityOptions)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _identityUserRepository = identityUserRepository;
    _configuration = configuration;
    _tenantRepository = tenantRepository;
    _emailService = emailService;
    _logger = logger;
    _identityOptions = identityOptions;
}

public async Task<IActionResult> OnPostAsync()
{
    using (CurrentTenant.Change(new Guid(TenantId)))
    {
        await _identityOptions.SetAsync();   // <-- add this

        var user = await _identityUserRepository.FindAsync(UserId);
        ...
        var changePasswordResult = await _userManager.ChangePasswordAsync(user, CurrentPassword, NewPassword);
        ...
    }
}

One unrelated note while we're looking at this page: OnGet/OnPost bind UserId and TenantId from query string with no ownership check, so a logged-in user could change any other user's password by editing the URL. You'll likely want to derive them from the current cookie / principal instead, the way ABP's built-in ChangePassword page does.

Thanks

Hi,

One more question — are you able to reproduce this in a fresh ABP template project (same version, Angular, Pro), or does it only happen in your PRIM project? If you can reproduce in a clean template, please share the exact steps; if not, it likely points to something specific in your project (custom services, modules, or middleware) and we can narrow down from there.

Thanks

Hi,

Thanks for the DB query — the rows look correct (host writes T + NULL, tenants write T + <tenantId>), so the value 8 is saved. The screenshot still showing 6 looks like a different code path or stale state at validation time.

Could you share:

  1. Which user are you logged in as when you hit Change Password — host admin, or a tenant user?
  2. The URL of the Change Password page in the screenshot — /account/change-password, /Account/ChangePassword, or somewhere else?
  3. Did you do a full browser refresh (F5) between saving the setting and opening Change Password?
  4. Server-side debug log for the failing change-password request — enable Volo.Abp.Settings and Volo.Abp.Identity at Debug level following https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems. The log will show the actual RequiredLength value used at validation time.
  5. Any customizations on IdentitySettingsAppService, SettingProvider, the Change Password page or ProfileAppService.ChangePasswordAsync?

Feel free to send logs to liming.ma@volosoft.com if too large for the thread.

Thanks

Hi,

The screenshot alone isn't enough for us to tell what's going wrong. Could you share a bit more so we can reproduce it:

  1. ABP version, UI type (Angular / MVC / Blazor) and whether the solution is tiered (separated auth server).
  2. What exactly does "not working" look like — does Save fail with an error, or does it save successfully but the new policy doesn't take effect when a user actually changes their password?
  3. Are you updating the host settings or a tenant's settings? Does the same happen on the other side?
  4. If Save fails, the browser DevTools → Network response for the settings update request, plus any related server-side log.
  5. If Save succeeds but the policy isn't applied, the exact steps you take afterwards (e.g. change password from My Account, create a user from admin UI) and what message you see.

If the logs / repro are too large for the thread, feel free to send them to liming.ma@volosoft.com.

Thanks

Answer

Hi,

The latest stable is 10.4 (2026-05-14). 10.5 is currently in RC and the only CMS Kit change in it is the CodeMirror 6 update. The items you listed (SEO meta, media gallery, RSS, content versioning, social streams) are listed in the road map backlog under "Application Modules / UI Themes" but not assigned to a specific release yet (SEO meta has its own feature request at https://github.com/abpframework/abp/issues/16342). When they land, you'll receive them through a regular version upgrade.

On the Angular side, CMS Kit support was introduced in 10.2 ("Angular: CMS Kit Module Features" in the release notes) and has been stable since then. Angular support here refers to the admin UI — from 10.2 onwards it covers:

  • Open-source: Blogs, Blog Posts, Pages, Tags, Menus, Comments, Global Resources, CMS Settings
  • Pro: Newsletters, Polls, FAQs, Page Feedbacks, URL Shorting, CMS Settings

So if you move from 9.3.5 to 10.4 you'll get the full admin set above on Angular. The public-facing pages and widgets (blog list/detail, commenting, ratings, reactions, popular tags, newsletter subscription, etc.) are served by the MVC public-web app that the microservice template already includes — that's the public website you enabled with --public-website, where the CMS Kit MVC views and ViewComponents are wired up.

A note on the upgrade itself: since you're on 9.3.5 (.NET 9), the main step is the move to .NET 10 in 10.0. Run through the 9.x → 10.x migration guide first, then go to 10.4.

For the integration steps in a microservice solution, the official guide covers everything you need (CLI command, EF migrations, static proxy, gateway routes, and connection string mapping):

  • https://abp.io/docs/latest/guides/add-module-to-microservice
  • https://abp.io/docs/latest/modules/cms-kit-pro (for enabling features via GlobalFeatureConfigurator)

Thanks

Showing 381 to 390 of 12915 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 November 02, 2026, 06:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.